├── images ├── timeEvolution.png └── energy_magnetization.png ├── configuration.txt ├── .gitignore ├── simulation.py ├── testing.py ├── plots.py ├── ising.py └── README.md /images/timeEvolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanFrassineti/2D-Ising-Model-simulation/HEAD/images/timeEvolution.png -------------------------------------------------------------------------------- /images/energy_magnetization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonathanFrassineti/2D-Ising-Model-simulation/HEAD/images/energy_magnetization.png -------------------------------------------------------------------------------- /configuration.txt: -------------------------------------------------------------------------------- 1 | [settings] 2 | N = 16 3 | M = 16 4 | numberTemp = 50 5 | startTemp = 0.01 6 | endTemp = 5 7 | latticeTemp = 0.5 8 | eqSteps = 512 9 | 10 | [paths] 11 | time_pic: ./images/timeEvolution.png 12 | enemag_pic: ./images/energy_magnetization.png 13 | 14 | my_time: ./data/time.npy 15 | my_ene: ./data/ene.npy 16 | my_mag: ./data/mag.npy 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # tex 7 | *.aux 8 | *.bbl 9 | *.fdb_latexmk 10 | *.blg 11 | *.log 12 | *.out 13 | *.fls 14 | *.toc 15 | *.synctex.gz 16 | *.tex.backup 17 | 18 | # markdown 19 | *.dvi 20 | *.pyg 21 | 22 | # C extensions 23 | *.so 24 | 25 | # Distribution / packaging 26 | .Python 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | MANIFEST 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .coverage 58 | .coverage.* 59 | .cache 60 | nosetests.xml 61 | coverage.xml 62 | *.cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | db.sqlite3 74 | 75 | # Flask stuff: 76 | instance/ 77 | .webassets-cache 78 | 79 | # Scrapy stuff: 80 | .scrapy 81 | 82 | # Sphinx documentation 83 | docs/_build/ 84 | 85 | # PyBuilder 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # pyenv 92 | .python-version 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | 122 | # directories 123 | [Bb]in/ 124 | [Bb]uild/ 125 | [Dd]ata/ 126 | .hypothesis/ -------------------------------------------------------------------------------- /simulation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Oct 21 16:25:29 2019 4 | 5 | @author: Jonathan Frassineti 6 | """ 7 | import ising 8 | import numpy as np 9 | import sys 10 | from sys import argv 11 | import configparser 12 | 13 | #main part of the code 14 | config = configparser.ConfigParser() 15 | config.read(sys.argv[1]) 16 | 17 | N = config.get('settings', 'N') 18 | M = config.get('settings', 'M') 19 | numberTemp = config.get('settings', 'numberTemp') 20 | startTemp = config.get('settings', 'startTemp') 21 | endTemp = config.get('settings', 'endTemp') 22 | eqSteps = config.get('settings', 'eqSteps') 23 | lattice_T = config.get('settings', 'latticeTemp') 24 | 25 | 26 | destination1 = config.get('paths','my_time') 27 | destination2 = config.get('paths','my_ene') 28 | destination3 = config.get('paths','my_mag') 29 | 30 | N = int(N) 31 | M = int(M) 32 | numberTemp = int(numberTemp) 33 | startTemp = float(startTemp) 34 | endTemp = float(endTemp) 35 | lattice_T = float(lattice_T) 36 | eqSteps = int(eqSteps) 37 | 38 | T = np.linspace(startTemp,endTemp,numberTemp) 39 | Energy = np.zeros(numberTemp) 40 | Magn = np.zeros(numberTemp) 41 | inverseTemp = 1.0/T 42 | n1 = 1.0/(eqSteps*N*M) 43 | 44 | for tempInterval in range(numberTemp): 45 | 46 | print("Temperature point: {}/{}".format(tempInterval,numberTemp)) 47 | 48 | config = ising.initialstate(N,M) 49 | Energy1 = Magn1 = 0 50 | 51 | for i in range(eqSteps): 52 | ising.montmove(config,inverseTemp[tempInterval]) 53 | 54 | for i in range(eqSteps): 55 | ising.montmove(config,inverseTemp[tempInterval]) 56 | Energy2 = ising.calculateEnergy(config) # calculate the energy 57 | Magn2 = ising.calculateMagn(config) # calculate the magnetisation 58 | Energy1 += Energy2 59 | Magn1 += Magn2 60 | 61 | Energy[tempInterval] = n1*Energy1 62 | Magn[tempInterval] = n1*Magn1 63 | 64 | print("Simulation done!") 65 | 66 | np.save(destination2,Energy) 67 | np.save(destination3,Magn) 68 | 69 | print("Energies and magnetization saved.") 70 | 71 | print("Now, simulate the system in the ordered phase along time.") 72 | totalStates = ising.simulate(ising.initialstate(N,M),lattice_T) 73 | np.save(destination1,np.asarray(totalStates)) 74 | 75 | print("Simulation saved.") -------------------------------------------------------------------------------- /testing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Oct 15 14:09:18 2019 4 | 5 | @author: Jonathan Frassineti 6 | """ 7 | 8 | import ising 9 | import configuration 10 | import numpy as np 11 | import hypothesis 12 | from hypothesis import strategies as st 13 | from hypothesis import settings 14 | from hypothesis import given 15 | 16 | @given(N=st.integers(1,configuration.N), M = st.integers(1,configuration.M)) 17 | @settings(max_examples = 1) 18 | def test_initialstate(N,M): 19 | #Initialazing the model with N*M spins of values 1 and -1." 20 | model = ising.initialstate(N,M) 21 | #Test if the dimensions of the lattice are N and M." 22 | assert len(model) == N 23 | assert len(model[0]) == M 24 | abs_model = np.abs(model) 25 | #Test if all the spins have really the values 1 and -1." 26 | assert abs_model.all() == 1 27 | 28 | @given(N=st.integers(1,configuration.N), M = st.integers(1,configuration.M), numbTemp = st.integers(50,configuration.numberTemp), mcSteps = st.integers(100,configuration.eqSteps)) 29 | @settings(max_examples = 1) 30 | def test_montmove(N, M, numbTemp, mcSteps): 31 | #Do a cycle in order to range from lowest temperature to highest one." 32 | for tempInterval in range(numbTemp): 33 | #Calculation of beta = 1/kT, where k is put equal to 1 for semplicity." 34 | model = ising.initialstate(N,M) 35 | #Copy the original state in order to compare it with the modified one." 36 | init = model.copy() 37 | #Montecarlo moves from the original state to the modified one." 38 | for i in range (mcSteps): 39 | config = ising.montmove(model,configuration.inverseTemp[tempInterval]) 40 | #Test if the final state is different from the initial one. 41 | assert ising.calculateEnergy(config) <= ising.calculateEnergy(init) 42 | assert np.abs(ising.calculateMagn(config)) >= np.abs(ising.calculateMagn(init)) 43 | 44 | @given(N=st.integers(1,configuration.N), M = st.integers(1,configuration.M)) 45 | @settings(max_examples = 1) 46 | def test_simulate(N, M): 47 | #This method simulates the Ising model of a given configuration for a specific T." 48 | initState = ising.initialstate(N,M) 49 | finalState = ising.simulate(initState) 50 | assert ising.calculateEnergy(finalState[len(finalState)-1]) <= ising.calculateEnergy(finalState[0]) 51 | assert np.abs(ising.calculateMagn(finalState[len(finalState)-1])) >= np.abs(ising.calculateMagn(finalState[0])) 52 | 53 | 54 | 55 | 56 | 57 | if __name__ == "main": 58 | pass 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /plots.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Oct 24 12:04:25 2019 4 | 5 | @author: Jonathan Frassineti 6 | """ 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | import sys 10 | from sys import argv 11 | import configparser 12 | 13 | 14 | configu = configparser.ConfigParser() 15 | configu.read(sys.argv[1]) 16 | 17 | N = configu.get('settings', 'N') 18 | M = configu.get('settings', 'M') 19 | numberTemp = configu.get('settings', 'numberTemp') 20 | startTemp = configu.get('settings', 'startTemp') 21 | endTemp = configu.get('settings', 'endTemp') 22 | lattice_T = configu.get('settings', 'latticeTemp') 23 | 24 | source1 = configu.get('paths','my_time') 25 | source2 = configu.get('paths','my_ene') 26 | source3 = configu.get('paths','my_mag') 27 | 28 | destination1 = configu.get('paths','time_pic') 29 | destination2 = configu.get('paths','enemag_pic') 30 | 31 | 32 | N = int(N) 33 | M = int(M) 34 | numberTemp = int(numberTemp) 35 | startTemp = float(startTemp) 36 | endTemp = float(endTemp) 37 | lattice_T = float(lattice_T) 38 | 39 | T = np.linspace(startTemp,endTemp,numberTemp) 40 | 41 | def configurationPlot(): 42 | """This module plots the configuration once 43 | passed to it along with time, for a given T. 44 | 45 | Parameters: 46 | f: figure to plot. 47 | configuration: state of the configuration created by 48 | initialstate(N). 49 | i: time interval. 50 | N: length of the square lattice (N*N). 51 | n: number of subplot.""" 52 | config = np.load(source1) 53 | i = [0,1,4,32,100,1000] 54 | f = plt.figure(figsize=(15, 10), dpi=80) 55 | X, Y = np.meshgrid(range(N), range(M)) 56 | for n in range(len(i)): 57 | sp = f.add_subplot(3, 3, n+1) 58 | plt.setp(sp.get_yticklabels(), visible=False) 59 | plt.setp(sp.get_xticklabels(), visible=False) 60 | plt.pcolormesh(X, Y, config[n], cmap=plt.cm.RdBu) 61 | plt.title('Time=%d'%i[n], fontsize = 15) 62 | plt.axis('tight') 63 | # plt.show() 64 | plt.suptitle("Spins configuration at T = {} K".format(lattice_T), fontsize = 20) 65 | f.savefig(destination1) 66 | 67 | def graphPlot(): 68 | """ This method plots the magnetization and the energy of the lattice. 69 | """ 70 | energy = np.load(source2) 71 | magnetization = np.load(source3) 72 | f = plt.figure(figsize=(18, 12)) # plot the calculated values 73 | sp = f.add_subplot(2, 2, 1 ) 74 | plt.scatter(T, energy, s=50, marker='o', color='IndianRed') 75 | plt.xlabel("Temperature (T)", fontsize=20) 76 | plt.ylabel("Energy ", fontsize=20) 77 | plt.axis('tight') 78 | sp = f.add_subplot(2, 2, 2 ) 79 | plt.scatter(T, abs(magnetization), s=50, marker='o', color='RoyalBlue') 80 | plt.xlabel("Temperature (T)", fontsize=20) 81 | plt.ylabel("Magnetization ", fontsize=20) 82 | plt.axis('tight') 83 | plt.show() 84 | f.savefig(destination2) 85 | 86 | configurationPlot() 87 | graphPlot() 88 | 89 | print("Figures saved!") 90 | -------------------------------------------------------------------------------- /ising.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Oct 15 14:01:39 2019 4 | 5 | @author: Jonathan Frassineti 6 | """ 7 | import numpy as np 8 | from numpy.random import rand 9 | 10 | def initialstate(N,M): 11 | """This method generates a random spin configuration for the initial condition. 12 | 13 | Parameters 14 | N : length of the lattice. 15 | M : width of the lattice. 16 | 17 | Returns: 18 | The state of the initial configuration of the (N*M) spins. 19 | 20 | Raise: 21 | ValueError if length or width of the lattice is less than 1.""" 22 | if N < 1 or M < 1: 23 | raise ValueError('Both dimensions of the lattice must be > 1, but are {} and {}'.format(N,M)) 24 | np.random.seed(1) 25 | initState = np.random.choice([-1,1],size=(N,M)) 26 | return initState 27 | 28 | def montmove(config,beta): 29 | """This method creates a Monte Carlo 30 | move using the Metropolis algorithm. 31 | 32 | Parameters: 33 | config: state of the configuration created by initialstate(N,M). 34 | beta: 1/kT, where T is temperature and Boltzmann constant k is out equal to 1. 35 | 36 | Returns: 37 | The modified state of the lattice where the energy is lower than the initial one.""" 38 | length = len(config) 39 | width = len(config[0]) 40 | for i in range(length): 41 | for j in range(width): 42 | x = np.random.randint(0,length) 43 | y = np.random.randint(0,width) 44 | #State of the (x,y) spin in the lattice. 45 | spin = config[x,y] 46 | #State of the nearest neighbours spins in the lattice (there are 4). 47 | others = config[(x+1)%length,y] + config[x,(y+1)%width] + config[(x-1)%length,y] + config[x,(y-1)%width] 48 | #Energy change if spin (x,y) is flipped, according to the surrounding spins." 49 | energyCost = 2*spin*others 50 | #If the energy change is negative, accept the move and flip the spin, otherwise accept the move with probability exp(-cost*beta), and flip the spin. 51 | if energyCost < 0 : 52 | spin *= -1 53 | elif rand() < np.exp(-energyCost*beta): 54 | spin *= -1 55 | #New state of the spin." 56 | config[x,y] = spin 57 | return config 58 | 59 | def calculateEnergy(config): 60 | """This method calculates the energy of a given configuration, given the exchange constant J = 1. 61 | 62 | Parameters: 63 | config: state of the configuration created by initialstate(N,M). 64 | 65 | Returns: 66 | The calculated energy of the modified configuration of the lattice.""" 67 | energy = 0 68 | for i in range(len(config)): 69 | for j in range(len(config[0])): 70 | spinEnergy = config[i,j] 71 | othersEnergy = config[(i+1)%len(config),j] + config[i,(j+1)%len(config[0])] + config[(i-1)%len(config),j] + config[i,(j-1)%len(config[0])] 72 | #The change in energy is given by the product of the (x,y) spin and the 4 nearest neighbours spins. 73 | energy += -spinEnergy*othersEnergy 74 | return energy/4 75 | 76 | def calculateMagn(config): 77 | """This method calculates the magnetization of a given configuration of spins. 78 | 79 | Parameters: 80 | config: state of the configuration created by initialstate(N,M). 81 | 82 | Returns: 83 | The magnetization of the modified lattice, which is the sum of all the spins.""" 84 | magnetization = np.sum(config) 85 | return magnetization 86 | 87 | def simulate(config,lattice_T): 88 | """This module simulates the Ising model lattice 89 | for a given temperature, under the critical temperature, 90 | where the systemis ordered (ferromagnetic state). 91 | This also saves an array with the simulation data of the lattice. 92 | 93 | Parameters: 94 | config: state of the configuration created by initialstate(N,M). 95 | 96 | Returns: 97 | The different states during time. 98 | """ 99 | temperature = lattice_T # Initialise the lattice with a specific temperature. 100 | initState = config.copy() 101 | evolutionSteps = 1001 102 | states = [initState] 103 | for i in range(evolutionSteps): 104 | #print("Simulation at time {}".format(i)) 105 | modState = montmove(config, 1.0/temperature) 106 | if i == 1: 107 | state2 = modState.copy() 108 | states.append(state2) 109 | if i == 4: 110 | state3 = modState.copy() 111 | states.append(state3) 112 | if i == 32: 113 | state4 = modState.copy() 114 | states.append(state4) 115 | if i == 100: 116 | state5 = modState.copy() 117 | states.append(state5) 118 | if i == 1000: 119 | state6 = modState.copy() 120 | states.append(state6) 121 | return states 122 | 123 | print("Simulation done!") 124 | 125 | 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2D Ising model 2 | 3 | The Ising Hamiltonian can be written as, 4 | 5 | ![equation1](https://latex.codecogs.com/gif.latex?H&space;=&space;-J\sum_{\left&space;\langle&space;ij&space;\right&space;\rangle}S_{i}S_{j}) 6 | - The spins ***Si*** can take values ±1, 7 | - ***⟨ij⟩*** implies nearest-neighbor interaction only, 8 | - ***J***>0 is the strength of exchange interaction. 9 | 10 | The system undergoes a 2nd order phase transition at the critical temperature ***Tc***. For temperatures less than ***Tc***, the system magnetizes, and the state is called the ferromagnetic or the ordered state. This amounts to a globally ordered state due to the presence of local interactions between the spin. For temperatures greater than ***Tc***, the system is in the disordered or the paramagnetic state. In this case, there are no long-range correlations between the spins. 11 | 12 | The order parameter 13 | 14 | ![equation2](https://latex.codecogs.com/gif.latex?m&space;=&space;\frac{\left&space;\langle&space;S&space;\right&space;\rangle}{N}) 15 | 16 | for this system is the average magnetization. The order parameter distinguishes the two phases realized by the systems. It is zero in the disordered state, while non-zero in the ordered, ferromagnetic, state. 17 | The one dimensional (1D) Ising model does not exhibit the phenomenon of phase transition while higher dimension do; this can be argued based on arguments related to the change in free energy 18 | 19 | ![equation3](https://latex.codecogs.com/gif.latex?F&space;=&space;E&space;-&space;TS) 20 | 21 | Here ***E*** and ***S*** are, respectively, the energy and entropy of the system. We estimate the net change in free energy for introducing a disorder in an otherwise ordered system. The ordered state can only be stable if the net change in free energy is positive, ***ΔF>0***, for any non-zero temperature. 22 | 23 | In this project we will simulate the ***2D*** Ising model using a Monte-Carlo simulation. 24 | 25 | ## Monte-Carlo simulation of 2D Ising model 26 | 27 | The following code simulates the Ising model in 2D using the Metropolis algorithm. The main steps of Metropolis algorithm are: 28 | 29 | 1. Prepare an initial configuration of ***N*** spins 30 | 2. Flip the spin of a randomly chosen lattice site. 31 | 3. Calculate the change in energy ***dE***. 32 | 4. If ***dE < 0***, accept the move. Otherwise accept the move with probability ***exp^{-dE/T}***. This satisfies the detailed balance condition, ensuring a final equilibrium state. 33 | 34 | Repeat 2-4. 35 | 36 | For references about Metropolis algorithm, see [this link](https://www.asc.ohio-state.edu/braaten.1/statphys/Ising_MatLab.pdf). 37 | 38 | ## Structure of the project 39 | These are the steps in order to start the program and to plot the results: 40 | 1) First, the user has to choose between the different configurations of the lattice (in our case there is only one, configuration.txt) and eventually write a new one, using the syntax of [configuration](https://github.com/JonathanFrassineti/Software-Project/blob/master/configuration.txt); if the user wants to do so, 41 | he has to specify the lattice parameters (N, M, numberTemp, startTemo, endTemp and eqSteps) and also the local paths to the folders where data and graphs must be saved. 42 | 2) Then, to start the Ising model the user has to launch the file [simulation](https://github.com/JonathanFrassineti/Software-Project/blob/master/simulation.py) which imports its parameters from [configuration](https://github.com/JonathanFrassineti/Software-Project/blob/master/configuration.txt) using ConfigParser library; there could be different types of configurations for the model, depending on the size of the lattice, the MonteCarlo steps or the folder where data are saved, so the user has to specify the configuration he wants when launching the simulation file from the command line with the syntax ***"python simulation.py name_of_the_configuration"*** (in our case, configuration.txt). 43 | The collected data (time evolution, energy and magnetization) are saved automatically in the ***data*** folder using their local paths. 44 | 3) At the end, the user has to launch the [plots](https://github.com/JonathanFrassineti/Software-Project/blob/master/plots.py) file with the configuration he wants; from command line, the syntax is ***"python plots.py name_of_the_configuration"*** (in our case, configuration.txt). 45 | The data are loaded from the configuration file through their local paths and then they are saved in the ***images*** folder automatically. 46 | 47 | This is how I divided my project into blocks: 48 | 49 | - In the file [ising](https://github.com/JonathanFrassineti/Software-Project/blob/master/ising.py) I have built the Ising functions that calculate energy and magnetization of the system, and save them in arrays in order to use them for further data analysis. In addition, for a given temperature there is a function that calculates the different states of a lattice during time, from disordered state to ordered state. 50 | 51 | - In the file [testing](https://github.com/JonathanFrassineti/Software-Project/blob/master/testing.py) I have tested all the Ising functions to ensure that all of them work properly, using hypothesis testing. 52 | 53 | - In the file [configuration](https://github.com/JonathanFrassineti/Software-Project/blob/master/configuration.txt) there are all the definitions of the parameters used in the simulation file, as number of spins per lattice (N*M), temperature intervals and so on. Furthermore, there are the local paths in order to load the array data and to save them as images and graphs. It's a .txt file that is imported in simulation file. 54 | 55 | - In the file [simulation](https://github.com/JonathanFrassineti/Software-Project/blob/master/simulation.py) there is the main part of the code, where I have used the functions of ising file in order to calculate the energy and the magnetization of a configuration of spins for a range of temperatures across the critical one ***Tc***, showing a steeply decrease in energy from high temperatures to low ones and a rapidly increase in magnetization, a clear sign of a phase transition. In addition there is the calculation of the different states of the configuration of spins for a given temperatrue, lower than ***Tc***, respect to time, which shows that the system coarsens toward the configuration of all spins aligned; then I saved these states in an array to process them in further data analysis. 56 | Here I used the ConfigParser library in order to import the configuration file from command line, and passing its parameters to the program. 57 | 58 | - In file [plots](https://github.com/JonathanFrassineti/Software-Project/blob/master/plots.py) there are the two functions that respectively plot the time evolution of the system and the energy and the magnetization, loading the data from the saved arrays from command line. 59 | 60 | To show you some results: 61 | 1) this is the plot of energy vs T and magnetization vs T. 62 | ![config](./images/energy_magnetization.png) 63 | 64 | 2) and this is how the simulation of a given configuration looks like, for a given temperature and during the time: 65 | ![config](./images/timeEvolution.png) 66 | 67 | --------------------------------------------------------------------------------