Нахождение физических величин с помощью Монте-Карло. Модель Изинга

Нахождение физических величин с помощью Монте-Карло. Модель Изинга#

Модель Изинга - это решётка взаимодействующих спинов NxN c периодическими граничными условиями.

image.png

\[ E(S)=-J \sum_{i \sim j} S_i S_j \]

Алгоритм Метрополиса в данном случае позволяет генерировать элементы из NVT ансамбля согласно распределению Гиббса.

С помощью этого можем получить средние величины, просто усредняя их по получившейся выборке.

import numpy as np
import matplotlib.pyplot as plt
from numba import njit, prange

@njit()
def E_calc(system: np.array, N: int) -> float: # Подсчёт энергии с учётом ПГУ
    
    E = 0
    
    for i in range(N):
        for j in range(N):
            
            if (i != 0) and (j != 0):
                E -= (system[i-1, j]*system[i, j] + system[i, j]*system[i, j-1])
                continue
            if (i == 0) and (j != 0):
                E -= (system[N-1, j]*system[i, j] + system[i, j]*system[i, j-1])
                continue
            if (i != 0) and (j == 0):
                E -= (system[i-1, j]*system[i, j] + system[i, j]*system[i, N-1])
                continue
            
    return E

@njit()
def izing(N: int,T: float, steps=5000000):
    
    system = np.ones((N, N)) # Инициализируем систему с сонаправленными спинами. Наш x_0
     
    E = E_calc(system, N) # Считаем энергию
    
    for i in range(steps): # Алгоритм Метрополиса (версия Гиббса)
        
        # выбираем случайный элемент
        i1 = np.random.randint(0, N) 
        i2 = np.random.randint(0, N)
        
        # определяем индексы соседей с учётом ПГУ
        i_up = i1 - 1 
        i_down = i1 + 1
        i_left = i2 - 1
        i_right = i2 + 1
        
        if i_up == -1:
            i_up = N-1
        
        if i_down == N:
            i_down = 0
        
        if i_left == -1:
            i_left = N-1
            
        if i_right == N:
            i_right = 0
        
        # смотрим на изменение энергии при изменении значения спина i1, i2
        deltaE = 2*system[i1, i2]*(system[i_up, i2] + system[i_down, i2] + system[i1, i_left] + system[i1, i_right])
            
        if deltaE < 0: # Если энергия стала ниже, то всегда принимаем новую конфигурацию
            system[i1, i2] *= -1 # измененный system наше x_{t+1} состояние
            E += deltaE # отслеживаем значение энергии
        else:
            if np.random.uniform(0,1) < np.exp(-deltaE/T): # Если нет, то используем рпспределение Гиббса как P(x)
                system[i1, i2] *= -1 # измененный system наше x_{t+1} состояние
                E += deltaE
                
    return E, system.mean() # выводим энергию на последнем получившемся x_t и средний спин на состоянии x_t
from tqdm import tqdm

N = 100
T = 10

Ms = []
Es = []
Ts = np.linspace(1, 3, 50)

#avg E from T
for T in tqdm(Ts):
    
    E, M = 0, 0
    
    # Усредняем 100 раз по разным экспериментам, чтобы избавиться от корреляции
    for i in range(100): 
        deltaE, deltaM = izing(N,T, steps=1000000)
        E += deltaE
        M += deltaM
    E /= 100
    M /= 100
    
    Es.append(E)
    Ms.append(M)
100%|███████████████████████████████████████████████████████████████████████████████████| 50/50 [04:29<00:00,  5.39s/it]
# Теоретические выкладки (ищите сами, откуда :))

from scipy.optimize import fsolve
from scipy.special import ellipk
from scipy.special import ellipe

def T_crit(T_cand):
    return np.sinh(2/T_cand) - 1
T_c_theory = fsolve(T_crit, 2)[0]

def E_ud_theory(T):
    
    kappa = 2*np.sinh(2./T)/(np.cosh(2./T)**2)
    E = 0
    E += -2*np.tanh(2./T)
    E += -(np.sinh(2./T)**2 - 1)/np.sinh(2./T)/np.cosh(2./T)*(2./np.pi*ellipk(kappa**2) - 1)

    return E

def C_theory(T):
    
    kappa = 2*np.sinh(2./T)/(np.cosh(2./T)**2)
    
    C = 4./np.pi
    
    C *= (1./T/np.tanh(2./T))**2
    
    C *= (ellipk(kappa**2) - ellipe(kappa**2) - (1 - np.tanh(2./T)**2)*(np.pi/2 + (2*np.tanh(2./T)**2 - 1)*ellipk(kappa**2)))
    return C
Es = np.array(Es)

Es_theory = E_ud_theory(Ts)

plt.plot(Ts, Es/N**2, label = "exp")
plt.plot(Ts, Es_theory, label = "theory")
plt.legend()
plt.title("E_уд(T)")
Text(0.5, 1.0, 'E_уд(T)')
../../_images/211751a605f7677988ec854f614693d5f3b978ac98487c35ebdcf2be96160bc9.png
plt.plot(Ts, Ms)
plt.title("M(T)")
Text(0.5, 1.0, 'M(T)')
../../_images/4671e4c403ef0ad77e02868de40c79b12ee5ae264d42590195955e9826540ec3.png
dT = Ts[1] - Ts[0]
Cvs = (Es[2:]-Es[0:-2])/2/dT/N**2

plt.plot(Ts[1:-1], Cvs)
plt.plot(Ts[1:-1], C_theory(Ts[1:-1]))
plt.title("C(T)")
print(f"Tc_exp = {Ts[Cvs.argmax()]}")
print(f"Tc_theory = {T_c_theory}")
Tc_exp = 2.3061224489795915
Tc_theory = 2.269185314213022
../../_images/d8782cda5f89995ea9ed979c9bf5a0170298fb19809f24e399f72b0b789d98a3.png