#!/usr/bin/env python3

import numpy as np , pylab, tkinter
import math
import matplotlib.pyplot as plt
import mpmath as mp

hbar=0.658212           # Planck's constant (eV.fs/rad)
J0=0.05                 # per-neighbor exchange interaction (eV)

# exchange interaction parameters
J1 = 11.254 # in eV 
J2 = 0.0    # adim
J3 = 1.0    # in Ang.

# initial spins
S1 = np.array([1.0, 0.0, 0.0])
S2 = np.array([0.0, 1.0, 0.0])

alpha=0.01              # damping coefficient
pi=math.pi

N=30000                 # number of timesteps
dt=0.1                  # timestep (fs)

# Rodrigues rotation formula
def rotation_matrix(axis, theta):
  """
  Return the rotation matrix associated with counterclockwise
  rotation about the given axis by theta radians
  """
  axis = np.asarray(axis)
  a = math.cos(theta / 2.0)
  b, c, d = -axis * math.sin(theta / 2.0)
  aa, bb, cc, dd = a * a, b * b, c * c, d * d
  bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d
  return np.array([[aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)],
      [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)],
      [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc]])

#Definition of the Bethe-Slater function
def func_BS(x,a,b,c):
    return 4*a*((x/c)**2)*(1-b*(x/c)**2)*np.exp(-(x/c)**2)

#Definition of the derivative of the Bethe-Slater function
def func_dBS(x,a,b,c):
    return 4*a*((x/c)**2)*(1-b*(x/c)**2)*np.exp(-(x/c)**2)

# calculating precession field of spin Sr
def calc_rot_vector(Sr,Sf):
  rot = (J0/hbar)*(Sf-alpha*np.cross(Sf,Sr))/(1.0+alpha**2)
  return rot
  
# second-order ST decomposition as implemented in LAMMPS 
for t in range (0,N):
  # advance s1 by dt/4
  wf1 = calc_rot_vector(S1,S2)
  theta=dt*np.linalg.norm(wf1)*0.25
  axis=wf1/np.linalg.norm(wf1)
  S1 = np.dot(rotation_matrix(axis, theta), S1)
  # advance s2 by dt/2
  wf2 = calc_rot_vector(S2,S1)
  theta=dt*np.linalg.norm(wf2)*0.5
  axis=wf2/np.linalg.norm(wf2)
  S2 = np.dot(rotation_matrix(axis, theta), S2)
  # advance s1 by dt/2
  wf1 = calc_rot_vector(S1,S2)
  theta=dt*np.linalg.norm(wf1)*0.5
  axis=wf1/np.linalg.norm(wf1)
  S1 = np.dot(rotation_matrix(axis, theta), S1)
  # advance s2 by dt/2
  wf2 = calc_rot_vector(S2,S1)
  theta=dt*np.linalg.norm(wf2)*0.5
  axis=wf2/np.linalg.norm(wf2)
  S2 = np.dot(rotation_matrix(axis, theta), S2)
  # advance s1 by dt/4
  wf1 = calc_rot_vector(S1,S2)
  theta=dt*np.linalg.norm(wf1)*0.25
  axis=wf1/np.linalg.norm(wf1)
  S1 = np.dot(rotation_matrix(axis, theta), S1)
  # calc. average magnetization
  Sm = (S1+S2)*0.5
  # calc. energy
  en = -J0*(np.dot(S1,S2))
  # print res. in ps for comparison with LAMMPS
  print(t*dt/1000.0,Sm[0],Sm[1],Sm[2],en)
