Código:Modelo de Blume-Capel
Ir para navegação
Ir para pesquisar
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from IPython.display import HTML
from numba import njit, prange
# Cria uma tabela de referência com o mesmo tamanho
# da matriz de estado. Essa matriz terá, para uma partícula de
# índice 1, uma lista com os índices referente á um passo para cada
# lado, usado para acelerar a simulação.
@njit
def genLookup(side):
index = np.arange(side**2)
up = (index - side) % (side**2)
down = (index + side) % (side**2)
left = np.where(index % side == 0, index - 1 + side, index - 1)
right = np.where((index + 1) % side == 0, index + 1 - side, index + 1)
return np.stack((up, down, left, right))
# Função que simula a mudança de um número predefinido de spins aleatoriamente
# Aceita a mudança se ela diminuir a energia, recusa se ela
# não ultrapassar o parâmetro de corte.
@njit
def timeSteps(state, lookup, steps = 1, J = 1, D = 1, T = 1):
N = state.size
varE = 0.0
# Repete a função para N passos
for i in range(steps):
# A cada passo, escolhe uma partícula e um spin aleatórios
index = np.random.randint(0, N)
oldSpin = state[index]
newSpin = np.random.randint(-1, 2)
# Pula se os spins forem iguais
if newSpin == oldSpin:
continue
# Soma o estado dos vizinhos através da tabela de referência
neiSum = state[lookup[0][index]] +\
state[lookup[1][index]] +\
state[lookup[2][index]] +\
state[lookup[3][index]]
# Calcula a mudança de energia do passo
dE = -J * (newSpin - oldSpin) * neiSum + D * (newSpin**2 - oldSpin**2)
# Verifica se o passo foi ou não aceito
if np.random.random() < np.exp(-dE / T):
state[index] = newSpin
varE += dE
# Retorna o estado depois de n passos e a variação de energia total
return state, varE
# Função que cria a animação
def aniSpaceEvol(states, D, temp, ene, step=10):
steps = np.shape(states)[0]
time = np.linspace(0,steps,steps)
side = int(np.sqrt(np.shape(states[0]))[0])
layout = """
SS0
SS1
SS2
"""
fig, axes = plt.subplot_mosaic(layout,
figsize=(6, 4),
dpi=72,
layout="constrained")
mag = np.abs( np.mean(states , axis=1))
qua = np.mean(np.abs( states), axis=1)
sysAx = axes['S']
eneAx = axes['0']
magAx = axes['1']
quaAx = axes['2']
sysLine = sysAx.imshow(
states[0].reshape((side,side)),
cmap=plt.get_cmap('plasma',3),
vmin=-1,
vmax=1,
)
cbar = fig.colorbar(sysLine, ax=sysAx)
cbar.set_ticks([-2./3, 0, 2./3])
cbar.set_ticklabels(['Spin -1', 'Spin 0', 'Spin +1'])
eneLine, = eneAx.plot([],[])
magLine, = magAx.plot([],[])
quaLine, = quaAx.plot([],[])
sysAx.set_xlim(-0.5,side-0.5)
eneAx.set_xlim(time[0],time[-1])
magAx.set_xlim(time[0],time[-1])
quaAx.set_xlim(time[0],time[-1])
magAx.sharex(eneAx)
quaAx.sharex(eneAx)
sysAx.set_ylim(-0.5,side-0.5)
eneAx.set_ylim(np.min(ene),np.max(ene))
magAx.set_ylim(0, 1)
quaAx.set_ylim(0, 1)
sysAx.set_title("Distribuição de spin")
eneAx.set_ylabel("Energia")
magAx.set_ylabel("Magnetização")
quaAx.set_ylabel("Quadripolo")
eneAx.tick_params(labelbottom=False)
magAx.tick_params(labelbottom=False)
quaAx.set_xlabel("Passos (MCS)")
sysAx.set_axis_off()
eneAx.grid()
magAx.grid()
quaAx.grid()
def update(frame):
sysLine.set_array(states[frame].reshape((side,side)))
eneLine.set_data(time[:frame], ene[:frame])
magLine.set_data(time[:frame], mag[:frame])
quaLine.set_data(time[:frame], qua[:frame])
sysAx.set_title(f"Temperatura = {temp:1.3f}; D = {D:.3f}")
return [sysLine, eneLine, magLine, quaLine]
anim = animation.FuncAnimation(
fig,
update,
frames=range(0, states.shape[0], step),
interval=50,
blit=True
)
plt.close(fig)
return anim
# Função utilizada para gerar os dados do espaço de fases.
# Utiliza o njit pela complexidade matemática.
# Recebe listas com os valores de D e T que se que simular
# além do tamanho da grade da simulação interna e quantos MCS se quer realizar
@njit
def genPhaseSpace(TStates, DStates, simSize, mcs, J=1.0):
N = simSize ** 2
nT = TStates.size
nD = DStates.size
M = np.zeros((steps, nT, nD))
Q = np.zeros((steps, nT, nD))
lookup = genLookup(simSize)
# utriliza-se o prange para aproveitar dos mútiplos núcleos da CPU
# Itera sobre os valores de T e D que se quer realizar a simulação
for i in prange(nT):
for j in range(nD):
# Cria um estrado inicial para o sitema, igual para todos
state = np.ones(N, dtype=np.int64)
# Calcula-se a Magnetização e o quadrupolo médio do sistema inicial
M[0, i, j] = np.sum(state) / N
Q[0, i, j] = np.sum(np.abs(state)) / N
# Realiza um número recebido de passos de monte carlo no sistema
for k in range(1,mcs):
# A energia após cada passo é descartada, pois não nos interessa aqui
state, _ = timeSteps(
state, lookup,
steps=N,
J=J,
D=DStates[j],
T=TStates[i]
)
# Salva os valors de M e Q no tempo k
M[k, i, j] = np.sum(state) / N
Q[k, i, j] = np.sum(np.abs(state)) / N
return M, Q
def aniPhaseSpace(M, Q, T, D, points = [], step=5):
N = M.shape[0]
extent = [D[0], D[-1], T[0], T[-1]]
frames = range(1, N + 1, step)
MFrames = [np.mean(M[:k], axis=0) for k in frames]
QFrames = [np.mean(Q[:k], axis=0) for k in frames]
MQFrames = [np.abs(m - q) for m, q in zip(MFrames, QFrames)]
fig, axes = plt.subplots(1, 3, figsize=(14, 5), dpi=75, sharey=True)
imshow_kwargs = dict(origin='lower', aspect='auto', extent=extent,
interpolation='nearest', cmap="viridis", vmin = 0, vmax=1)
im0 = axes[0].imshow(MFrames[0], **imshow_kwargs)
im1 = axes[1].imshow(QFrames[0], **imshow_kwargs)
im2 = axes[2].imshow(MQFrames[0], **imshow_kwargs)
axes[0].set(ylabel="Temperatura T")
plt.colorbar(im2, ax=axes[-1], fraction=0.04, pad=0.04)
imTitle = fig.suptitle('', fontsize=12)
for ax, im, title in zip(axes,
[im0, im1, im2],
[
'<|M|> (Magnetização)',
'<Q> (Momento quadripolar)',
'|<M> - <Q>| (Diferença entre pontos)'
]):
for i, point in enumerate(points):
ax.plot(point[0], point[1], 'bo', label='Ponto simulados' if i == 0 else None)
ax.plot(1.965, 0.61, 'ro', label='PTC (teórico)')
ax.set(xlabel='D', title=title)
ax.legend()
def update(idx):
im0.set_array(MFrames[idx])
im1.set_array(QFrames[idx])
im2.set_array(MQFrames[idx])
imTitle.set_text(f'Tempo {list(frames)[idx]}/{N}')
return [im0, im1, im2, imTitle]
anim = animation.FuncAnimation(
fig, update,
frames=len(list(frames)),
interval=80,
blit=True
)
plt.tight_layout()
plt.close(fig)
return anim
gridSize = 20
mcs = 30_000
states = np.zeros((mcs,gridSize**2),dtype=int)
D, T, J = (1.965, 0.61, 1)
lookup = genLookup(gridSize)
states[0], E_i = timeSteps(
states[0].copy(),
lookup,
D = D,
T = T,
steps=50*gridSize**2
)
E = np.zeros(mcs)
side = int(np.sqrt(states[0].size))
E[0] = D * np.sum(states[0]**2)
E[0] += - J * np.sum(states[0] * (states[0][lookup[1]] + states[0][lookup[2]]))
for i in range(1,mcs):
states[i], E_i = timeSteps(
states[i-1].copy(),
lookup,
D = D,
T = T,
steps=gridSize**2
)
E[i] = E[i-1] + E_i
anim = aniSpaceEvol(states, step = 100, D=D, temp = T, ene = E/(gridSize**2))
anim.save('TCP.gif', writer='ffmpeg')
simSize = 20
gridSize = 50
mcs = 500
J = 1.0
TStates = np.linspace(0.1, 5.1, gridSize)
DStates = np.linspace(0.0, 5.0, gridSize)
M,Q = genPhaseSpace(TStates, DStates, simSize, mcs)
anim = aniPhaseSpace(
M, Q,
TStates, DStates,
points = [(1.965,0.91), (1.665,0.61), (2.265,0.61)],
step = 5
)
HTML(anim.to_jshtml())
anim.save('phaseSpace.gif', writer='ffmpeg')