<?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=Aab1817</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=Aab1817"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Aab1817"/>
	<updated>2026-05-18T00:00:44Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796683</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796683"/>
		<updated>2019-11-20T11:59:02Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|thumb|center|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_uglyfit.png|thumb|center|&#039;&#039;Figure 9&#039;&#039;&#039;:  Comparison of the experimental vs the given C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
The discrepancy in the plot shows that the experimental data needs correction, but both exhibit a peak in heat capacity.&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With the C++ data already imported, it was fitted as such:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(T, C, 3) # fit a third order polynomial&lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(T_range,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;)&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_firstfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The region for which the data was fitted was T=[2.0,2.8]°K&lt;br /&gt;
&lt;br /&gt;
It was fitted as follows:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    Tmin = 2.0 &lt;br /&gt;
    Tmax = 2.8 &lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; Tmin, T &amp;lt; Tmax) #choose only those rows where both conditions are true&lt;br /&gt;
    peak_T_values = T[selection]&lt;br /&gt;
    peak_C_values = C[selection]&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(peak_T_values, peak_C_values, 3) # fit a third order polynomial&lt;br /&gt;
    fitted_C_values = np.polyval(fit, peak_T_values) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(peak_T_values,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;) &lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_secondfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_Tinfinity.png|thumb|center|&#039;&#039;Figure 11&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
Using the following code:&lt;br /&gt;
&lt;br /&gt;
    twomax= np.max(twoc[:,5])&lt;br /&gt;
    Tmax2 = twoc[:,0][twoc[:,5] == twomax]&lt;br /&gt;
    fourmax= np.max(fourc[:,5])&lt;br /&gt;
    Tmax4 = fourc[:,0][fourc[:,5] == fourmax]&lt;br /&gt;
    eightmax= np.max(eightc[:,5])&lt;br /&gt;
    Tmax8 = twoc[:,0][eightc[:,5] == eightmax]&lt;br /&gt;
    sixtmax= np.max(sixtc[:,5])&lt;br /&gt;
    Tmax16 = sixtc[:,0][sixtc[:,5] == sixtmax]&lt;br /&gt;
    thirtmax= np.max(thirtc[:,5])&lt;br /&gt;
    Tmax32 = thirtc[:,0][thirtc[:,5] == thirtmax]&lt;br /&gt;
    sixtymax= np.max(sixtyc[:,5])&lt;br /&gt;
    Tmax64 = sixtyc[:,0][sixtyc[:,5] == sixtymax]&lt;br /&gt;
    c= np.array([twomax,fourmax, eightmax,sixtmax, thirtmax, sixtymax])&lt;br /&gt;
    L = np.array([2,4,8,16,32,64])&lt;br /&gt;
    t=np.array([Tmax2, Tmax4, Tmax8, Tmax16, Tmax32, Tmax64]).flatten()&lt;br /&gt;
    from scipy.optimize import curve_fit&lt;br /&gt;
    def func(x, a, b):&lt;br /&gt;
        return a/x + b&lt;br /&gt;
    popt, pcov = curve_fit(func, L, t)&lt;br /&gt;
    l = np.linspace(L[0],L[-1],100)&lt;br /&gt;
    plt.plot(l, func(l, *popt), &#039;k--&#039;, label=&#039;Fit&#039;)&lt;br /&gt;
    plt.plot(L, t, &amp;quot;kx&amp;quot;, label=&amp;quot;Data&amp;quot;)&lt;br /&gt;
    plt.grid()&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.xlabel(&amp;quot;Lattice size (arb. units)&amp;quot;)&lt;br /&gt;
    plt.ylabel(r&amp;quot;Curie temperature $T_C$ ($K$)&amp;quot;)&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
We get that the experimental Curie temperature is &amp;lt;math&amp;gt; 2.25616915\pm 0.01593072&amp;lt;/math&amp;gt; Kelvin.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hence, the value extracted from the data is similar, within error to the theoretical value.&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796676</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796676"/>
		<updated>2019-11-20T11:56:24Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|thumb|center|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_uglyfit.png|thumb|center|&#039;&#039;Figure 9&#039;&#039;&#039;:  Comparison of the experimental vs the given C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With the C++ data already imported, it was fitted as such:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(T, C, 3) # fit a third order polynomial&lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(T_range,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;)&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_firstfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The region for which the data was fitted was T=[2.0,2.8]°K&lt;br /&gt;
&lt;br /&gt;
It was fitted as follows:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    Tmin = 2.0 &lt;br /&gt;
    Tmax = 2.8 &lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; Tmin, T &amp;lt; Tmax) #choose only those rows where both conditions are true&lt;br /&gt;
    peak_T_values = T[selection]&lt;br /&gt;
    peak_C_values = C[selection]&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(peak_T_values, peak_C_values, 3) # fit a third order polynomial&lt;br /&gt;
    fitted_C_values = np.polyval(fit, peak_T_values) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(peak_T_values,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;) &lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_secondfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_Tinfinity.png|thumb|center|&#039;&#039;Figure 11&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
Using the following code:&lt;br /&gt;
&lt;br /&gt;
    twomax= np.max(twoc[:,5])&lt;br /&gt;
    Tmax2 = twoc[:,0][twoc[:,5] == twomax]&lt;br /&gt;
    fourmax= np.max(fourc[:,5])&lt;br /&gt;
    Tmax4 = fourc[:,0][fourc[:,5] == fourmax]&lt;br /&gt;
    eightmax= np.max(eightc[:,5])&lt;br /&gt;
    Tmax8 = twoc[:,0][eightc[:,5] == eightmax]&lt;br /&gt;
    sixtmax= np.max(sixtc[:,5])&lt;br /&gt;
    Tmax16 = sixtc[:,0][sixtc[:,5] == sixtmax]&lt;br /&gt;
    thirtmax= np.max(thirtc[:,5])&lt;br /&gt;
    Tmax32 = thirtc[:,0][thirtc[:,5] == thirtmax]&lt;br /&gt;
    sixtymax= np.max(sixtyc[:,5])&lt;br /&gt;
    Tmax64 = sixtyc[:,0][sixtyc[:,5] == sixtymax]&lt;br /&gt;
    c= np.array([twomax,fourmax, eightmax,sixtmax, thirtmax, sixtymax])&lt;br /&gt;
    L = np.array([2,4,8,16,32,64])&lt;br /&gt;
    t=np.array([Tmax2, Tmax4, Tmax8, Tmax16, Tmax32, Tmax64]).flatten()&lt;br /&gt;
    from scipy.optimize import curve_fit&lt;br /&gt;
    def func(x, a, b):&lt;br /&gt;
        return a/x + b&lt;br /&gt;
    popt, pcov = curve_fit(func, L, t)&lt;br /&gt;
    l = np.linspace(L[0],L[-1],100)&lt;br /&gt;
    plt.plot(l, func(l, *popt), &#039;k--&#039;, label=&#039;Fit&#039;)&lt;br /&gt;
    plt.plot(L, t, &amp;quot;kx&amp;quot;, label=&amp;quot;Data&amp;quot;)&lt;br /&gt;
    plt.grid()&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.xlabel(&amp;quot;Lattice size (arb. units)&amp;quot;)&lt;br /&gt;
    plt.ylabel(r&amp;quot;Curie temperature $T_C$ ($K$)&amp;quot;)&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
We get that the experimental Curie temperature is &amp;lt;math&amp;gt; 2.25616915\pm 0.01593072&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_Tinfinity.png&amp;diff=796672</id>
		<title>File:01346889 Tinfinity.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_Tinfinity.png&amp;diff=796672"/>
		<updated>2019-11-20T11:52:35Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_uglyfit.png&amp;diff=796657</id>
		<title>File:01346889 uglyfit.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_uglyfit.png&amp;diff=796657"/>
		<updated>2019-11-20T11:41:14Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796634</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796634"/>
		<updated>2019-11-20T11:23:05Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|thumb|center|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With the C++ data already imported, it was fitted as such:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(T, C, 3) # fit a third order polynomial&lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(T_range,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;)&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_firstfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The region for which the data was fitted was T=[2.0,2.8]°K&lt;br /&gt;
&lt;br /&gt;
It was fitted as follows:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    Tmin = 2.0 &lt;br /&gt;
    Tmax = 2.8 &lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; Tmin, T &amp;lt; Tmax) #choose only those rows where both conditions are true&lt;br /&gt;
    peak_T_values = T[selection]&lt;br /&gt;
    peak_C_values = C[selection]&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(peak_T_values, peak_C_values, 3) # fit a third order polynomial&lt;br /&gt;
    fitted_C_values = np.polyval(fit, peak_T_values) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(peak_T_values,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;) &lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_secondfit.png|thumb|center|&#039;&#039;Figure 10&#039;&#039;&#039;:  Fitted polynomial for the whole range of temperature of the C++ data for a 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_secondfit.png&amp;diff=796632</id>
		<title>File:01346889 secondfit.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_secondfit.png&amp;diff=796632"/>
		<updated>2019-11-20T11:22:55Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796626</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796626"/>
		<updated>2019-11-20T11:12:24Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|thumb|center|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With the C++ data already imported, it was fitted as such:&lt;br /&gt;
&lt;br /&gt;
    T = eightc[:,0] #get the first column&lt;br /&gt;
    C = eightc[:,5] # get the second column&lt;br /&gt;
    #first we fit the polynomial to the data&lt;br /&gt;
    fit = np.polyfit(T, C, 3) # fit a third order polynomial&lt;br /&gt;
    #now we generate interpolated values of the fitted polynomial over the range of our function&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
    plt.plot(eightc[:,0],eightc[:,5],&#039;k&#039;, label=&amp;quot;C++ data&amp;quot;)&lt;br /&gt;
    plt.plot(T_range,fitted_C_values,&#039;b&#039;, label=&amp;quot;fit&amp;quot;)&lt;br /&gt;
    plt.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    plt.ylabel(&amp;quot;Heat capacity&amp;quot;)&lt;br /&gt;
    plt.legend()&lt;br /&gt;
    plt.show()&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_firstfit.png&amp;diff=796625</id>
		<title>File:01346889 firstfit.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_firstfit.png&amp;diff=796625"/>
		<updated>2019-11-20T11:12:21Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796595</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796595"/>
		<updated>2019-11-20T10:38:18Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|thumb|center|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796594</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796594"/>
		<updated>2019-11-20T10:38:07Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|thumb|center|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|thumb|center|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796593</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796593"/>
		<updated>2019-11-20T10:37:40Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.|centre]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.|centre]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken|centre]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice |none]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice|none]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796589</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796589"/>
		<updated>2019-11-20T10:35:57Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|thumb|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|thumb|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796588</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796588"/>
		<updated>2019-11-20T10:35:30Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|thumb|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796587</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796587"/>
		<updated>2019-11-20T10:35:12Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Calculating the energy and magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|thumb|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796580</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796580"/>
		<updated>2019-11-20T10:32:04Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796577</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796577"/>
		<updated>2019-11-20T10:30:49Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to the Ising model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt;, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796573</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796573"/>
		<updated>2019-11-20T10:29:23Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_heat.png|&#039;&#039;Figure 8&#039;&#039;&#039;:  Heat capacity against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_heat.png&amp;diff=796572</id>
		<title>File:01346889 heat.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_heat.png&amp;diff=796572"/>
		<updated>2019-11-20T10:28:48Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796564</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796564"/>
		<updated>2019-11-20T10:22:02Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta} = U&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt; (\Delta E)^2 = &amp;lt;E^2&amp;gt; - &amp;lt;E&amp;gt;^2&amp;lt;/math&amp;gt; but  &amp;lt;math&amp;gt; &amp;lt;E^2&amp;gt;= \frac{d^2 Z}{d \beta^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(\Delta E)^2 = \frac{d^2 \ln(Z)}{d \beta^2}= -\frac{dU}{d\beta}= -\frac{dU}{dT} \frac{dT}{d\beta}= k_B T^2 \frac{dU}{dT}= k_B T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thus, &amp;lt;math&amp;gt; (\Delta E)^2= Var[E]= k_b T^2 C&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796553</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796553"/>
		<updated>2019-11-20T10:14:49Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{d Z}{d \beta}= -\frac{d \ln(Z)}{d \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796552</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796552"/>
		<updated>2019-11-20T10:14:29Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{\dd Z}{\dd \beta}= -\frac{\dd \ln(Z)}{\dd \beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt; &lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796550</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796550"/>
		<updated>2019-11-20T10:14:14Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; &amp;lt;E&amp;gt;= \sum_j p_j \epsilon_j&amp;lt;/math&amp;gt; where&amp;lt;math&amp;gt;p_j = \frac{e^-{\epsilon_j \beta}}{Z}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;Z&amp;lt;/math&amp;gt; is the partition function.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;E&amp;gt;= -\frac{1}{Z} \frac{\ddZ}{\dd\beta}= -\frac{\dd \ln(Z)}{\dd\beta}&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796527</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796527"/>
		<updated>2019-11-20T10:02:34Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_energies.png|&#039;&#039;Figure 6&#039;&#039;&#039;:  Average energy per spin against temperature for different lattice sizes ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_magn.png|&#039;&#039;Figure 7&#039;&#039;&#039;:  Average magnetisation per spin against temperature for different lattice sizes]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is at least 16x16, potentially larger, as we see major improvement in accounting for larger fluctuations.&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_magn.png&amp;diff=796520</id>
		<title>File:01346889 magn.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_magn.png&amp;diff=796520"/>
		<updated>2019-11-20T10:01:06Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_energies.png&amp;diff=796514</id>
		<title>File:01346889 energies.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_energies.png&amp;diff=796514"/>
		<updated>2019-11-20T09:59:46Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796497</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796497"/>
		<updated>2019-11-20T09:50:46Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8energy.png|&#039;&#039;Figure 4&#039;&#039;&#039;: Average energy per spin against temperature for an 8x8 lattice]]&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_8magn.png|&#039;&#039;Figure 5&#039;&#039;&#039;:  Average magnetisation per spin against temperature for an 8x8 lattice ]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_8magn.png&amp;diff=796496</id>
		<title>File:01346889 8magn.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_8magn.png&amp;diff=796496"/>
		<updated>2019-11-20T09:50:33Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796494</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796494"/>
		<updated>2019-11-20T09:48:39Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_8energy.png&amp;diff=796493</id>
		<title>File:01346889 8energy.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_8energy.png&amp;diff=796493"/>
		<updated>2019-11-20T09:48:30Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796385</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796385"/>
		<updated>2019-11-20T02:47:19Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
The Kramers-Wannier duality gives one possible relationship for the Curie temperature.&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;
In our approximation, we take J=1.0, so our theoretical value is &amp;lt;math&amp;gt;T_C = ..&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796384</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796384"/>
		<updated>2019-11-20T02:45:39Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if self.n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
k_B T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796378</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796378"/>
		<updated>2019-11-20T02:36:48Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0125921890714&lt;br /&gt;
E*E =  0.0311936913342&lt;br /&gt;
M =  0.0126340931948&lt;br /&gt;
M*M =  0.0176704450218&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
k_B T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796376</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796376"/>
		<updated>2019-11-20T02:29:24Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=1500. &lt;br /&gt;
This was chosen as the number of steps required for the average values of energy and magnetisation to reach their expected convergence, i.e. for magnetisation, zero.&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
   def montecarlostep(self,T):&lt;br /&gt;
       energy =self.energy()&lt;br /&gt;
       random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
       random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
       self.lattice[random_i, random_j] *= -1&lt;br /&gt;
       energy1= self.energy()&lt;br /&gt;
       E_diff=energy1-energy&lt;br /&gt;
       if E_diff&amp;lt;0:&lt;br /&gt;
             energy =energy1&lt;br /&gt;
      else:&lt;br /&gt;
           R=np.random.random()&lt;br /&gt;
           if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
               energy=energy1&lt;br /&gt;
           else:&lt;br /&gt;
                energy=energy&lt;br /&gt;
      if n_cycles &amp;lt;= 1500:&lt;br /&gt;
          self.energies= self.energies&lt;br /&gt;
      else:&lt;br /&gt;
          self.energies.append(energy)&lt;br /&gt;
          self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
          self.n_cycles +=1&lt;br /&gt;
      return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def statistics(self):&lt;br /&gt;
            energies= np.asarray(self.energies)&lt;br /&gt;
            magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
            E=np.mean(energies)&lt;br /&gt;
            E2= np.mean(np.power(energies,2))&lt;br /&gt;
            M= np.mean(magnetisations)&lt;br /&gt;
            M2=np.mean(magnetisations**2)&lt;br /&gt;
        return  E, E2, M, M2, (self.n_cycles-1500)&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
k_B T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796374</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796374"/>
		<updated>2019-11-20T02:25:58Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
       self.energies.append(energy)&lt;br /&gt;
       self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
k_B T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796362</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796362"/>
		<updated>2019-11-20T01:54:31Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
&lt;br /&gt;
k_B T_c/J = \frac{2}{\ln(1+\sqrt{2})} \approx 2.26918531421.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796359</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796359"/>
		<updated>2019-11-20T01:26:15Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. You can view its source code here if you are interested. Each file contains six columns: T, E, E^2, M, M^2, C (the final five quantities are per spin), and you can read them with the NumPy loadtxt function as before. For each lattice size, plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit to your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Modify your script from the previous section. You should still plot the whole temperature range, but fit the polynomial only to the peak of the heat capacity! You should find it easier to get a good fit when restricted to this region.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
4. &amp;lt;i&amp;gt;Find the temperature at which the maximum in C occurs for each datafile that you were given. Make a text file containing two colums: the lattice side length (2,4,8, etc.), and the temperature at which C is a maximum. This is your estimate of &amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; for that side length. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Temperatures&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
Theoretical exact Curie temperature for the infinite 2D ising lattice.&lt;br /&gt;
Comparison&lt;br /&gt;
Biggest sources of error&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796355</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796355"/>
		<updated>2019-11-20T01:22:49Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Determining the heat capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
1.  &amp;lt;i&amp;gt;By definition, &amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;. From this, show that &amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt; (Where &amp;lt;math&amp;gt;\mathrm{Var}[E]&amp;lt;/math&amp;gt; is the variance in E.)&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 — this is normal, and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PLOT HC vs T, for lattice sizes&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796352</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796352"/>
		<updated>2019-11-20T01:18:28Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
1. &amp;lt;i&amp;gt; Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy per spin versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report. How big a lattice do you think is big enough to capture the long range fluctuations?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FIG 2x2&lt;br /&gt;
&lt;br /&gt;
FIG 4X4&lt;br /&gt;
&lt;br /&gt;
FIG 8X8&lt;br /&gt;
&lt;br /&gt;
FIG 16X16&lt;br /&gt;
&lt;br /&gt;
FIG 32x32&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GENERAL PLOT&lt;br /&gt;
&lt;br /&gt;
The estimated minimum lattice size is...&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796349</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796349"/>
		<updated>2019-11-20T01:16:54Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
1. &amp;lt;i&amp;gt;The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperature and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state? Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The number of cycles needed to reach the equilibrium state is taken as N=... &lt;br /&gt;
This choice was made by ...&lt;br /&gt;
&lt;br /&gt;
The functions were modified in the following way:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an 8\times 8 lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
PLOT&lt;br /&gt;
&lt;br /&gt;
It is estimated&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796346</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796346"/>
		<updated>2019-11-20T01:14:48Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796345</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796345"/>
		<updated>2019-11-20T01:14:33Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes &amp;lt;math&amp;gt; 2.76687630769 \pm 0.052078053156 &amp;lt;/math&amp;gt; seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of &lt;br /&gt;
&amp;lt;math&amp;gt;2.7336015 \pm 0.0145477689686 &amp;lt;/math&amp;gt;seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_anim.png&amp;diff=796342</id>
		<title>File:01346889 anim.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_anim.png&amp;diff=796342"/>
		<updated>2019-11-20T01:11:36Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: Aab1817 uploaded a new version of File:01346889 anim.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796340</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796340"/>
		<updated>2019-11-20T01:10:52Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of ... seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796338</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796338"/>
		<updated>2019-11-20T01:10:43Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
         energy=0.0&lt;br /&gt;
         rows= len(self.lattice)&lt;br /&gt;
         columns= len(self.lattice[0])&lt;br /&gt;
         for m in range(rows):&lt;br /&gt;
             for n in range(columns):&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0],self.lattice[0][1])&lt;br /&gt;
                 energy += np.multiply(self.lattice[0][0], self.lattice[1][0])&lt;br /&gt;
                 self.lattice= np.roll(self.lattice,1,axis=1)&lt;br /&gt;
             self.lattice= np.roll(self.lattice,1,axis=0)&lt;br /&gt;
         return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
        magnetisation=0.0&lt;br /&gt;
        for i in self.lattice:&lt;br /&gt;
             magnetisation += np.sum(i)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the code improvement, the computation time is of ... seconds.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796334</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796334"/>
		<updated>2019-11-20T01:01:03Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_anim.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796333</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796333"/>
		<updated>2019-11-20T01:00:46Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 3&#039;&#039;&#039;: Output figure of the ILanim.py file, plotting energy and magnetisation per spin against number of steps taken]]&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796331</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796331"/>
		<updated>2019-11-20T00:59:48Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
By animating our simulation, we see that the value of M tends to approach zero at low temperatures.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
E =  0.0421478060046&lt;br /&gt;
E*E =  0.26457852194&lt;br /&gt;
M =  0.018920804465&lt;br /&gt;
M*M =  0.0170845812885&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_anim.png&amp;diff=796330</id>
		<title>File:01346889 anim.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:01346889_anim.png&amp;diff=796330"/>
		<updated>2019-11-20T00:59:43Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796329</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796329"/>
		<updated>2019-11-20T00:58:12Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Before improving the code, it takes ... seconds to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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!).&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were modified as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;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!&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796326</id>
		<title>Rep:Aab1817 Monte Carlo</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Aab1817_Monte_Carlo&amp;diff=796326"/>
		<updated>2019-11-20T00:44:41Z</updated>

		<summary type="html">&lt;p&gt;Aab1817: /* Introduction to Monte Carlo simulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction to the Ising model==&lt;br /&gt;
1. &amp;lt;i&amp;gt;Show that the lowest possible energy for the Ising model is E\ =\ -DNJ, where D is the number of dimensions and N is the total number of spins. What is the multiplicity of this state? Calculate its entropy.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We know that the energy is given by &amp;lt;math&amp;gt;- \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;. For the lowest energy state, we have a system with all spins either up or down.&lt;br /&gt;
If we start with a model where D=1 and N=4, we get 8 possible interactions. Because all the spins are the same, this illustrates how the sum term of the energy is equal to 8, and we can relate it to 2DN, it this case 2 x 1 x 4. &lt;br /&gt;
Since we have figured out that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; equals 2DN, &amp;lt;math&amp;gt; \frac{1}{2} \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; must equal DN.&lt;br /&gt;
Hence, we obtain the expression mentioned above, where the lowest possible energy is given by E= - DNJ.&lt;br /&gt;
&lt;br /&gt;
The multiplicity of the state refers to the number of possible combination it can adopt: as mentioned before, the system could have all of its spins up or all of its spins down. The multiplicity is thus 2.&lt;br /&gt;
The entropy is linked to the multiplicity by the relation &amp;lt;math&amp;gt;S= k_B\ln \Omega&amp;lt;/math&amp;gt;, we get &amp;lt;math&amp;gt;S=k_b\ln(2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (D=3,\ N=1000)? How much entropy does the system gain by doing so?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Adopting a similar reasoning pattern to that of the previous question, if all spins were the same, the sum term of the energy would be equal to 2DN, so 6000. But by flipping one of the spins, we will have 12 terms in the summation that will change from +1 to -1, that is 6 terms for the interactions of the flipped spin with its neighbours, and 6 for the interactions of the neighbours with the flipped spin. Now we have 5988 terms in the sum equaling +1 and 12 terms equaling -1.&lt;br /&gt;
The sum term is now equal to 5976, and from that we get the value of the energy of the new system: &amp;lt;math&amp;gt;E= -\frac{1}{2}J \times 5976= -2988J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The lowest energy is &amp;lt;math&amp;gt;E_0 = -3000J&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The energy change is thus &amp;lt;math&amp;gt;\Delta E= 12J&amp;lt;/math&amp;gt; &lt;br /&gt;
&lt;br /&gt;
The entropy of the system is &amp;lt;math&amp;gt;S=k_B\ln(2000)&amp;lt;/math&amp;gt;, because there are 1000 spins to choose from when wanting to change one, and we could start with either all spins being up or down.&lt;br /&gt;
The entropy gain is thus &amp;lt;math&amp;gt; \Delta S= k_B\ln(1000)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;Calculate the magnetisation of the 1D and 2D lattices in figure 1. What magnetisation would you expect to observe for an Ising lattice with D = 3,\ N=1000 at absolute zero?&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|&#039;&#039;&#039;Figure 1&#039;&#039;&#039;: Illustration of an Ising lattice in one (N=5), two (N=5x5), and three (N=5x5x5) dimensions. Red cells indicate the &amp;quot;up&amp;quot; spin&amp;quot;, and blue cells indicate the &amp;quot;down&amp;quot; spin. Taken from the script&#039;s wiki.]]&lt;br /&gt;
Based on the image provided in the script we have:&lt;br /&gt;
&lt;br /&gt;
for the 1D case,  &amp;lt;math&amp;gt;M= \sum_i s_i = 1+ 1+1 -1 -1 =+1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
for the 2D case, &amp;lt;math&amp;gt; M=\sum_i s_i = 13-12= +1 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For the case in which D=3, N=1000, and T=0K. At this temperature, we assume the system takes the lowest energy configuration.&lt;br /&gt;
&amp;lt;math&amp;gt; M= \sum_i s_i= 1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M= \sum_i s_i= -1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;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 J=1.0 at all times (in fact, we are working in reduced units in which J=k_B, but there will be more information about this in later sections). Do not worry about the efficiency of the code at the moment — we will address the speed in a later part of the experiment.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions were completed as follows:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        energy=0.0&lt;br /&gt;
        for i in range(0,self.n_rows):&lt;br /&gt;
            for j in range(0, self.n_cols):&lt;br /&gt;
                if i == self.n_rows-1 and j == self.n_cols-1:&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1,self.n_cols-1]*self.lattice[1,self.n_cols-1]&lt;br /&gt;
                    energy += self.lattice[self.n_rows-1, self.n_cols-1]*self.lattice[self.n_rows-1,1]&lt;br /&gt;
               elif j == self.n_cols-1:&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i+1,self.n_cols-1]&lt;br /&gt;
                   energy += self.lattice[i,self.n_cols-1]*self.lattice[i,1]&lt;br /&gt;
               elif i == self.n_rows-1:&lt;br /&gt;
                   energy+= self.lattice[self.n_rows-1,j]*self.lattice[self.n_rows-1,j+1]&lt;br /&gt;
                   energy += self.lattice[self.n_rows-1,j]*self.lattice[1,j]&lt;br /&gt;
               else:&lt;br /&gt;
                   energy+= self.lattice[i,1]*self.lattice[i+1,j]&lt;br /&gt;
                   energy += self.lattice[i,j]*self.lattice[i,j+1]&lt;br /&gt;
        energy*= 1.0 # multiply by constant J=1.0&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
     def magnetisation(self):&lt;br /&gt;
          &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
          magnetisation=0.0&lt;br /&gt;
          for i in self.lattice:&lt;br /&gt;
              for j in i:&lt;br /&gt;
                  magnetisation += np.float(j)&lt;br /&gt;
          return magnetisation&lt;br /&gt;
&lt;br /&gt;
2.&amp;lt;i&amp;gt;Run the ILcheck.py script from the IPython Qt console using the command %run ILcheck.py&lt;br /&gt;
The displayed window has a series of control buttons in the bottom left, one of which will allow you to export the figure as a PNG image. Save an image of the ILcheck.py output, and include it in your report.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:01346889_ILcheck.png|&#039;&#039;Figure 2&#039;&#039;&#039;: Output figure of the ILcheck.py file, which confirms the validity of the code above.]]&lt;br /&gt;
&lt;br /&gt;
==Introduction to Monte Carlo simulation==&lt;br /&gt;
1. &amp;lt;i&amp;gt;How many configurations are available to a system with 100 spins? &amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a system of &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; distinguishable particles that can attain one of two possible spin states, there are &amp;lt;math&amp;gt; 2^n&amp;lt;/math&amp;gt; different spin configurations.&lt;br /&gt;
Hence for 100 particles, there are &amp;lt;math&amp;gt;2^{100}&amp;lt;/math&amp;gt; possible configurations, which evaluated is approximately 1.2676506e+30.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;i&amp;gt;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 1\times 10^9 configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T?&amp;lt;/math&amp;gt;&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The time needed to calculate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt; is the possible number of configurations divided by the computation speed. This evaluate to roughly &amp;lt;10^{21}&amp;lt;/math&amp;gt; seconds.&lt;br /&gt;
&lt;br /&gt;
2. &amp;lt;i&amp;gt;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 k_B! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;, and the number of Monte Carlo steps that have elapsed.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The function was defined as follows:&lt;br /&gt;
    def montecarlostep(self,T):&lt;br /&gt;
        energy =self.energy()&lt;br /&gt;
        random_i= np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j= np.random.choice(range(0,self.n_cols))&lt;br /&gt;
        self.lattice[random_i, random_j] *= -1&lt;br /&gt;
        energy1= self.energy()&lt;br /&gt;
        E_diff=energy1-energy&lt;br /&gt;
        if E_diff&amp;lt;0:&lt;br /&gt;
              energy =energy1&lt;br /&gt;
       else:&lt;br /&gt;
            R=np.random.random()&lt;br /&gt;
            if R &amp;lt;= np.exp(-E_diff/T):&lt;br /&gt;
                energy=energy1&lt;br /&gt;
            else:&lt;br /&gt;
                 energy=energy&lt;br /&gt;
            self.energies.append(energy)&lt;br /&gt;
            self.__magnetisations.append(self.magnetisation())&lt;br /&gt;
       self.n_cycles +=1&lt;br /&gt;
       return energy, self.magnetisation()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Where self.energies, self.__magnetisations, and self.n_cycles are defined at the top of the class, outside the Monte Carlo function.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The statistics function was defined as follows:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        energies= np.asarray(self.energies)&lt;br /&gt;
        magnetisations= np.asarray(self.__magnetisations)&lt;br /&gt;
        E=np.mean(energies)&lt;br /&gt;
        E2= np.mean(np.power(energies,2))&lt;br /&gt;
        M= np.mean(magnetisations)&lt;br /&gt;
        M2= np.mean(magnetisations**2)&lt;br /&gt;
        return E, E2, M, M2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
3. &amp;lt;i&amp;gt;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&amp;lt;/i&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a temperature under the Curie temperature, the spins are more likely to adapt a random configuration, i.e. random distribution of -1 and +1, which results in a value of M close to zero. Above the Curie temperature, the system loses its permanent properties, so under a magnetic field, the spins are likely to point in the same direction, leading to higher values of M.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code==&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature ==&lt;br /&gt;
&lt;br /&gt;
== The effect of system size==&lt;br /&gt;
&lt;br /&gt;
==Determining the heat capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie temperature==&lt;/div&gt;</summary>
		<author><name>Aab1817</name></author>
	</entry>
</feed>