# -*- coding: utf-8 -*-
"""
Created on Tue Sep 13 10:59:52 2016
@author: mpnun
"""
import os
import numpy as np
import matplotlib as mat
import matplotlib.pyplot as plt
[docs]class Lattice:
'''
Class which handles the KMC lattice
'''
fname_in = 'lattice_input.dat'
fname_out = 'lattice_output.txt'
def __init__(self):
'''
Initialize class variables
'''
self.text_only = True # If true, only store the text from the input file, if false, store all the complex information
self.lattice_in_txt = None
self.lattice_matrix = None # each row is a lattice vector
self.repeat = [1,1]
self.site_type_names = []
self.site_type_inds = []
self.frac_coords = []
self.cart_coords = []
self.neighbor_list = []
self.cell_list = [] # self, north, northeast, east, or southeast
[docs] def ReadIn(self, fldr):
'''
Read lattice_input.dat
:param fldr: Folder directory from which to read lattice_input.dat
'''
self.lattice_in_txt = []
with open(os.path.join(fldr, self.fname_in),'r') as Txt:
RawTxt = Txt.readlines()
for i in RawTxt:
self.lattice_in_txt.append(i.split('\n')[0])
[docs] def WriteIn(self, fldr):
'''
Write lattice_input.dat
:param fldr: Folder directory from which to read lattice_input.dat
'''
if self.text_only:
with open(os.path.join(fldr, self.fname_in), 'w') as txt:
for i in self.lattice_in_txt:
txt.write(i + '\n')
else:
self.Write_lattice_input(fldr)
[docs] def set_frac_coords(self, fc):
'''
Set the fractional and Cartesian coordinates
:param fc: n x 3 array (or list of lists) of fractional coordinates to set for each atom
'''
self.frac_coords = np.array(fc)
self.cart_coords = self.cart_coords = np.dot(self.frac_coords, self.lattice_matrix)
[docs] def set_cart_coords(self, cc):
'''
Set the Cartesian and fractional coordinates
:param fc: n x 3 array (or list of lists) of Cartesian coordinates to set for each atom
'''
self.cart_coords = np.array(cc)
self.frac_coords = np.dot(self.cart_coords, np.linalg.inv(self.lattice_matrix))
[docs] def coord_shift(self, a_ind, b_ind):
'''
Give the coordinates of the periodic image of B which is closest to A
:param a_ind: Index of atom A
:param b_ind: Index of atom B
'''
a_coords = self.cart_coords[ a_ind , : ]
image_coords = [ self.cart_coords[ b_ind , : ] for i in range(9) ]
dist_list = [None for i in range(9)]
ind = 0
for we in [-1, 0, 1]:
for sn in [-1, 0, 1]:
image_coords[ind] = image_coords[ind] + np.dot(np.array([we, sn]), self.lattice_matrix)
dist_list[ind] = np.linalg.norm( image_coords[ind] - a_coords )
ind += 1
min_d = dist_list[0]
min_d_ind = 0
for i in range(1,9):
if dist_list[i] < min_d:
min_d = dist_list[i]
min_d_ind = i
return image_coords[min_d_ind]
[docs] def PlotLattice(self, cutoff = 3.0, plot_neighbs = False, type_symbols = ['o','s','^','v', '<', '>', '8',
'd', 'D', 'H', 'h', '*', 'p', '+', ',', '.', '1', '2', '3', '4', '_', 'x', '|', 0, 1, 10, 11, 2, 3, 4, 5, 6, 7, 8], ms = 4):
'''
:param cutoff: Maximum distance to draw connections between nearest neighbor sites.
This prevents drawing line segments between sites which are neighbors only though their periodic images.
:param plot_neighbs: Flag to plot line segments between lattice sites which are first nearest neighbors to each other
:param type_symbols: List of symbols for each lattice site type.
:param ms: Marker size
:returns: pyplot object with the lattice graphed on it
'''
if self.text_only:
raise NameError('Lattice must be built before plotting. Use ReadAllOutput(build_lattice=True)')
if self.cart_coords == []:
self.cart_coords = np.dot(self.frac_coords, self.lattice_matrix)
border = np.dot(np.array([[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0],[0.0,0.0]]), self.lattice_matrix)
mat.rcParams['mathtext.default'] = 'regular'
mat.rcParams['text.latex.unicode'] = 'False'
mat.rcParams['legend.numpoints'] = 1
mat.rcParams['lines.linewidth'] = 2
mat.rcParams['lines.markersize'] = 12
plt.figure()
plt.plot(border[:,0], border[:,1], '--k', linewidth = 2) # cell border
if plot_neighbs:
ind = 0
for pair in self.neighbor_list: # neighbors
p1 = self.cart_coords[pair[0],:]
p2 = self.cart_coords[pair[1],:]
if self.cell_list[ind] == 'self':
plt.plot([p1[0], p2[0]], [p1[1], p2[1]], '-k', linewidth = 1)
ind += 1
for site_type in range(1, np.max(np.array(self.site_type_inds))+1 ):
is_of_type = []
for site_ind in range(len(self.site_type_inds)):
if self.site_type_inds[site_ind] == site_type:
is_of_type.append(site_ind)
plt.plot(self.cart_coords[is_of_type,0], self.cart_coords[is_of_type,1], linestyle='None', marker = type_symbols[(site_type-1) % len(type_symbols)], color = [0.8, 0.8, 0.8], markersize = ms) # sites
# Choose range to plot
xmin = np.min(border[:,0])
xmax = np.max(border[:,0])
delx = xmax - xmin
ymin = np.min(border[:,1])
ymax = np.max(border[:,1])
dely = ymax - ymin
mag = 0.1
plt.xlim([xmin - mag * delx, xmax + mag * delx])
plt.ylim([ymin - mag * dely, ymax + mag * dely])
plt.xticks(size=20)
plt.yticks(size=20)
plt.xlabel('x-coord (ang)',size=24)
plt.ylabel('y-coord (ang)',size=24)
plt.axis('equal')
plt.tight_layout()
return plt
[docs] def Read_lattice_output(self, fldr):
'''
Read lattice_output.txt
'''
with open( os.path.join(fldr, self.fname_out) ,'r') as txt:
RawTxt = txt.readlines()
n_sites = len(RawTxt) - 2
self.cart_coords = np.zeros([n_sites,2])
# Fill in lattice vectors
self.lattice_matrix = np.zeros([2,2])
line1 = RawTxt[0].split()
self.lattice_matrix[0,0] = float(line1[1])
self.lattice_matrix[0,1] = float(line1[2])
line2 = RawTxt[1].split()
self.lattice_matrix[1,0] = float(line2[1])
self.lattice_matrix[1,1] = float(line2[2])
# Fill in site coordinates and neighbors
self.neighbor_list = []
self.site_type_inds = []
for site_ind in range(n_sites):
line = RawTxt[site_ind+2].split()
self.cart_coords[site_ind,:] = [line[1], line[2]]
self.site_type_inds.append(int(line[3]))
neighbs = line[5::]
for site_2 in neighbs:
if int(site_2) > 0: # Zeros are placeholders in the output file
self.neighbor_list.append([site_ind+1, int(site_2)])
# Convert to fractional coordinates
self.frac_coords = np.dot(self.cart_coords, np.linalg.inv(self.lattice_matrix))
self.text_only = False
[docs] def Build_neighbor_list(self, cut = 3.0, cut_mat = []): # appending lists causes this method to be slow
'''
Builds the neighbor list based on distances between sites
:param cut: Maximum distance between nearest neighbor sites or their periodic images.
:param cut_mat: List of nearest neighbor cutoff distances between different site types. If empty, it will assume the same distance for
all site types.
'''
# Make sure these lists are empty before we start appending
self.neighbor_list = []
self.cell_list = [] # self, north, northeast, east, or southeast
if self.cart_coords == []:
self.cart_coords = np.dot(self.frac_coords, self.lattice_matrix)
n_site_types = len(self.site_type_names)
n_sites = len(self.site_type_inds)
self.cart_coords = np.dot(self.frac_coords, self.lattice_matrix)
if cut_mat == []:
cut_mat = cut * np.ones([n_site_types, n_site_types]) # Use a matrix to have different cutoff distances for different site types
# Loop through all sites
for site_1 in range(n_sites):
for site_2 in range(n_sites):
c1 = self.cart_coords[site_1,:]
c2 = self.cart_coords[site_2,:]
c_2_north = c2 + self.lattice_matrix[1,:]
c_2_northeast = c2 + self.lattice_matrix[0,:] + self.lattice_matrix[1,:]
c_2_east = c2 + self.lattice_matrix[0,:]
c_2_southeast = c2 + self.lattice_matrix[0,:] - self.lattice_matrix[1,:]
if site_1 < site_2: # check self
if np.linalg.norm( c1 - c2 ) < cut:
self.neighbor_list.append([site_1, site_2])
self.cell_list.append('self')
if np.linalg.norm( c1 - c_2_north ) < cut:
self.neighbor_list.append([site_1, site_2])
self.cell_list.append('north')
if np.linalg.norm( c1 - c_2_northeast ) < cut:
self.neighbor_list.append([site_1, site_2])
self.cell_list.append('northeast')
if np.linalg.norm( c1 - c_2_east ) < cut:
self.neighbor_list.append([site_1, site_2])
self.cell_list.append('east')
if np.linalg.norm( c1 - c_2_southeast ) < cut:
self.neighbor_list.append([site_1, site_2])
self.cell_list.append('southeast')