Código em Python para simulações de osciladores lineares e FPUT quadrático

De Física Computacional
Edição feita às 00h39min de 4 de maio de 2022 por Wallec (Discussão | contribs)

Ir para: navegação, pesquisa

Bibliotecas usadas:

import numpy as np
import random
import matplotlib.pyplot as plt
import os
import imageio

Funções:

#flag == 1 -> Duas massas acopladas por mola central (livres nos lados) -> Não está sendo usado neste trabalho
#flag == 2 -> Oscilador acoplado com duas massas e três molas (linear) -> Não precisaria mais existir... o flag 3 já #             resolve o caso N=2 também
#flag == 3 -> Oscilador acoplado com N>=2 massas e N+1 molas (linear)
#flag == 4 -> FPUT com termo quadrático

# L é o tamanho unidimensional de todo o sistema, de parede a parede;
# N é o número de partículas
# m é o número do modo que se quer inicializar: m=1,...,N (pode ser maior q N, mas vai repetir o primeiro modo)

def condicao_inicial_1d(L, N, m, flag):
  #Espaçamento das posições de equilíbrio das partículas
  step = L/(N+1)

  posx = np.zeros((N,1), dtype="float")
  posxeq = np.zeros((N,1), dtype="float")

  for i in range(0, N):
    posxeq[i] = (i+1)*step

  l = posxeq[1] - posxeq[0]

  #Duas massas acopladas por uma mola, ligeiramente comprimidas. Deixei por razões sentimentais, foi o primeiro que 
  #fiz.
  if(flag==1):                   
    posx[0] = posxeq[0]+0.5
    posx[1] = posxeq[1]-0.5
  #Duas massas, três molas, sistema acoplado:
  elif(flag==2):
    for i in range(0, N):
      posx[i] = modo_de_osc(posxeq[i], i, m, 0.5, N) 
  #N>=2 massas, N+1 molas, sistema acoplado, condição randômica de deslocamento do equilíbrio comentada:
  elif(flag==3):
    for i in range(N):
      #posx[i] = posxeq[i] + random.uniform(-l/5, +l/5) -> Se quiser condição inicial randômica, descomentar essa 
      #linha e comentar a linha seguinte
      posx[i] = modo_de_osc(posxeq[i], i, m, 0.5, N)
  #FPUT:
  elif(flag==4):
    for i in range(0, N):
      posx[i] = modo_de_osc(posxeq[i], i, m, 0.5, N)          

  return posx, posxeq

# Para inicializar o sistema em um determinado modo de oscilação no tempo t=0
# l é a posição da partícula na cadeia (de 0 a N-1), m é o número do modo que se quer: 1, 2, 3,...,N
# am é a amplitude da oscilação (tecnicamente, a amplitude do modo vai ser am*np.sin(...))
def modo_de_osc(poseq, l, m, am, N):
  return poseq + am*np.sin(np.pi*(l+1)*m/(N+1))

def condicao_inicial_2d(N):
  N_sqrt = int(np.sqrt(N))          # N tem q ser um quadrado perfeito!

  L = N_sqrt + 1
  l = step = 1

  posx = np.zeros((N,1), dtype="float")
  posxeq = np.zeros((N,1), dtype="float")

  posy = np.zeros((N,1), dtype="float")
  posyeq = np.zeros((N,1), dtype="float")

  for i in range(N):
    posxeq[i] = (i%N_sqrt)*step + 1
    posyeq[i] = int(i/N_sqrt)
    posx[i] = posxeq[i] + random.uniform(-l/5, +l/5)
    posy[i] = posyeq[i] + random.uniform(-l/5, +l/5)
  
  #Oscilador 2D também poderia ter inicialização por modo... mas não implementei.

  return L, posx, posy, posxeq, posyeq

def acel_quad(pos, poseq, k, m, alpha, flag):
  size = len(pos)
  l = poseq[1] - poseq[0]
  a = np.zeros((size,1), dtype="float")
  
  if(flag==1):
    a[0] = -k/m*(pos[0]-pos[1]+l)   #l é a distância entre as massas quando a mola está relaxada
    a[1] = k/m*(pos[0]-pos[1]+l)
  elif(flag==2):
    a[0] = k/m*((pos[1]-poseq[1])-2*(pos[0]-poseq[0]))     
    a[1] = k/m*((pos[0]-poseq[0])-2*(pos[1]-poseq[1])) 
  elif(flag==3 or flag==4):         #Se flag==3, cuidar para fazer alpha=0!
    x = pos-poseq                                      
    for i in range(size):                                 
      #Cuidar as fronteiras
      if(i==0):
        a[i] = k/m * (x[i+1] - 2*x[i] - alpha*(x[i]**2-(x[i+1]-x[i])**2))
      elif(i==(size-1)):
        a[i] = k/m * (x[i-1] - 2*x[i] - alpha*((x[i]-x[i-1])**2-x[i]**2))
      else:
        a[i] = k/m * (x[i+1] + x[i-1] - 2*x[i]) * (1.0 + alpha * (x[i+1] - x[i-1]));

  return a

def velocity_verlet(pos, poseq, vel, acel, k, m, alpha, flag):
  size = len(pos)
  new_acel = np.zeros((size,1), dtype="float")
  new_pos = np.zeros((size,1), dtype="float")
  new_vel = np.zeros((size,1), dtype="float")
  
  new_pos = pos + vel*dt + 0.5*acel*dt**2
  new_acel = acel_quad(new_pos, poseq, k, m, alpha, flag)
  new_vel = vel + 0.5*(acel + new_acel)*dt

  return new_pos, new_vel, new_acel

def plota_figura_1d(L, posX, posY, t, flag):
  figname = 'f{:.1f}.jpeg'.format(t)

  plt.figure(figsize=(5,3))
  plt.xlim(0, L)
  plt.ylim(-1, 1)
  #plt.yticks([])
  plt.axis("off")
  #plt.grid()

  if(flag==1):
    plt.title('Duas massas acopladas por uma mola - Tempo {:.1f}'.format(t))
    plt.scatter(posX, posY, s=300, c="black")
  elif(flag==2 or flag == 3):
    plt.title('Oscilador Acoplado Linear - Tempo {:.1f}'.format(t))
    plt.scatter(posX, posY, s=300, c="black")
  elif(flag==4):
    plt.title('FPUT quadrático - Tempo {:.1f}'.format(t))
    plt.scatter(posX, posY, s=5, c="black")
  
  plt.axvline(x=0)
  plt.axvline(x=L)

  if(flag != 4):
    size = len(posX)
    aj = 0.4            #Ajuste para plotar as linhas horizontais, varia com o tamanho do scatter, aqui, s=300

    for i in range(size):
      if(i==0):
        plt.axhline(y=0, xmin=0, xmax=(posX[i]-aj)/L, color='r', linestyle=':')
        plt.axhline(y=0, xmin=(posX[i]+aj)/L, xmax=(posX[i+1]-aj)/L, color='k', linestyle=':')
      elif(i==(size-1)):
        plt.axhline(y=0, xmin=(posX[i]+aj)/L, xmax=1, color='r', linestyle=':')
      else:
        plt.axhline(y=0, xmin=(posX[i]+aj)/L, xmax=(posX[i+1]-aj)/L, color='k', linestyle=':')

  plt.savefig(figname, dpi=100)           
  plt.close()
  return figname

def plota_figura_2d(L, posX, posY, t, flag):
  figname = 'f{:.1f}.jpeg'.format(t)

  plt.figure(figsize=(5,3))
  plt.xlim(0, L)
  plt.ylim(-1, L)
  #plt.yticks([])
  plt.axis("off")
  #plt.grid()

  plt.title('Oscilador Acoplado Linear 2D - Tempo {:.1f}'.format(t))
  
  plt.scatter(posX, posY, s=300, c="black")

  plt.savefig(figname, dpi=100)           
  plt.close()

  return figname

Imprimir os cinco primeiros modos de oscilação para N=32 partículas (Figura 2 da Wiki).

N=32

modo = np.zeros((5,N), dtype="float")

nodes = [i for i in range(1,33)]

for i in range(0,5):
  for j in range(0, N):
    modo[i,j] = modo[i,j] + np.sin((j+1)*np.pi*(i+1)/(N+1))
  
plt.plot(nodes, modo[0,:], "k.", label="Modo 1")
plt.plot(nodes, modo[1,:], "r-.", label="Modo 2")
plt.plot(nodes, modo[2,:], "b-", label="Modo 3")
plt.plot(nodes, modo[3,:], "g--", label="Modo 4")
plt.plot(nodes, modo[4,:], "y:", label="Modo 5")

plt.xlabel("Número da partícula")
plt.ylabel("Deslocamento em relação à posição de equilíbrio")

plt.legend()
plt.show()

Exemplo de como podem ser feitos os gráficos dos modos e como comparar com as frequências esperadas. Também estou calculando a energia total do sistema. O exemplo é do modo 1, do caso N=3. Note que a energia não é exatamente conservada, é uma dificuldade do método velocity Verlet. Para N=2 a conservação é bem obedecida, uma reta próxima a 0.2. Seria possível melhorar diminuindo o passo de integração, mas preferi deixar assim para ilustrar.

alpha = 0.0
k = 1.0
massa = 1.0
L = 12
N = 3

t=0.0
dt = 0.1
tmax = 30

posX = np.zeros((N,1), dtype="float")
posXeq = np.zeros((N,1), dtype="float")

posY = np.zeros((N,1), dtype="float")

velX = np.zeros((N,1), dtype="float")

acelX = np.zeros((N,1), dtype="float")
  
posX, posXeq = condicao_inicial_1d(L, N, 1, 3)

x1 = []
x2 = []
x3 = []
dotx1 = []
dotx2 = []
dotx3 = []
energia = []

x1.append(posX[0]-posXeq[0])
x2.append(posX[1]-posXeq[1])
x3.append(posX[2]-posXeq[2])
dotx1.append(velX[0])
dotx2.append(velX[1])
dotx3.append(velX[2])

energia.append(0.5*((posX[0]-posXeq[0])**2+(posX[1]-posXeq[1])**2+(posX[2]-posXeq[2])**2))

while(t < tmax):
  posX, velX, acelX = velocity_verlet(posX, posXeq, velX, acelX, k, massa, alpha, 3)

  x1.append(posX[0]-posXeq[0])
  x2.append(posX[1]-posXeq[1])
  x3.append(posX[2]-posXeq[2])
  dotx1.append(velX[0])
  dotx2.append(velX[1])
  dotx3.append(velX[2])

  energia.append(0.5*((posX[0]-posXeq[0])**2+(posX[1]-posXeq[1])**2+(posX[2]-posXeq[2])**2) + 0.5*(velX[0]**2+velX[1]**2+velX[2]**2))
  
  t += dt
time = np.linspace(0, tmax, len(energia))
plt.plot(time, energia)
plt.ylim(0, 1)
plt.show()
time = np.linspace(0, 30, len(x1))
plt.plot(time, 0.35*np.cos(np.sqrt(2-np.sqrt(2))*time), "g-", label=r"$cos(\sqrt{2-\sqrt{2}}t)$")
plt.plot(time, x1, "k.", label=r"$x_{1}$")
#plt.plot(time, x2, "r-", label=r"$x_{2}$")
#plt.plot(time, x3, "b.", label=r"$x_{2}$")


plt.xlim(0,30)

plt.xlabel("t")
plt.ylabel("Deslocamento em relação à posição de equilíbrio")

plt.title("Oscilador Linear (Modo 1) - N=3")
plt.legend(loc="lower right")
plt.show()

FPUT unidimensional, deslocamento horizontal:

Setar alpha diferente de zero e flag=4 (último parâmetro das funções condicao_inicial(), plota_figura_1d() e velocity_verlet()).

Salva animação no arquivo fput.gif. Bem grande o gif gerado.

As animações não têm linhas representando as molas e o tamanho das partículas é bem pequeno. Fiz isso de propósito pra visualizar melhor o caso com N=32 partículas, do artigo da RBF.

Lembrar de alterar as variáveis globais de acordo. Diferentemente do caso bidimensional, o valor de L tem q ser fornecido e deve ser obviamente diferente de zero.