Base library for calculating crystal properties
Download script from .\tkcrystalbase.py
from numpy import sin, cos, tan, arcsin, arccos, arctan, exp, log, sqrt
import numpy as np
from numpy import linalg as la
from pprint import pprint
"""
Base library for calculating crystal properties
"""
pi = 3.14159265358979323846
pi2 = pi + pi
torad = 0.01745329251944 # rad/deg";
todeg = 57.29577951472 # deg/rad";
basee = 2.71828183
h = 6.6260755e-34 # Js";
h_bar = 1.05459e-34 # "Js";
hbar = h_bar
c = 2.99792458e8 # m/s";
e = 1.60218e-19 # C";
me = 9.1093897e-31 # kg";
mp = 1.6726231e-27 # kg";
mn = 1.67495e-27 # kg";
u0 = 4.0 * 3.14*1e-7; # . "Ns2C-2";
e0 = 8.854418782e-12; # C2N-1m-2";
e2_4pie0 = 2.30711e-28 # Nm2";
a0 = 5.29177e-11 # m";
kB = 1.380658e-23 # JK-1";
NA = 6.0221367e23 # mol-1";
R = 8.31451 # J/K/mol";
F = 96485.3 # C/mol";
g = 9.81 # m/s2";
# Lattice parameters (angstrom and degree)
#lattice_parameters = [ 5.62, 5.62, 5.62, 60.0, 60.0, 60.0]
lattice_parameters = [ 5.62, 5.62, 5.62, 90.0, 90.0, 90.0]
# Site information (atom name, site label, atomic number, atomic mass, charge, radius, color, position)
sites = [
['Na', 'Na1', 11, 22.98997, +1.0, 0.7, 'red', np.array([0.0, 0.0, 0.0])]
,['Na', 'Na2', 11, 22.98997, +1.0, 0.7, 'red', np.array([0.0, 0.5, 0.5])]
,['Na', 'Na3', 11, 22.98997, +1.0, 0.7, 'red', np.array([0.5, 0.0, 0.5])]
,['Na', 'Na4', 11, 22.98997, +1.0, 0.7, 'red', np.array([0.5, 0.5, 0.0])]
,['Cl', 'Cl1', 17, 35.4527, +1.0, 1.4, 'blue', np.array([0.5, 0.0, 0.0])]
,['Cl', 'Cl2', 17, 35.4527, +1.0, 1.4, 'blue', np.array([0.5, 0.5, 0.5])]
,['Cl', 'Cl3', 17, 35.4527, +1.0, 1.4, 'blue', np.array([0.0, 0.0, 0.5])]
,['Cl', 'Cl4', 17, 35.4527, +1.0, 1.4, 'blue', np.array([0.0, 0.5, 0.0])]
]
def reduce01(x):
return x - int(x)
return x
def round01(x):
if abs(x - 1.0) < 0.0002:
return 1.0
if abs(x) < 0.0002:
return 0.0
if x > 1.0:
return x - int(x)
if x < 1.0:
return x - int(x) + 1.0
return x
def round(x, xmin, xmax):
if x > xmax:
return x - int(x - xmin)
if x < xmin:
return x - int(x - xmin) + 1.0
return x
def round_parameter(x, tol):
val = tol * int( (x+0.1*tol) / tol )
return val
def cal_lattice_vectors(latt):
cosb = cos(torad * latt[4])
cosg = cos(torad * latt[5])
sing = sin(torad * latt[5])
aij = np.empty([3, 3], dtype = float)
aij[0][0] = latt[0]
aij[0][1] = 0.0;
aij[0][2] = 0.0;
aij[1][0] = latt[1] * cosg
aij[1][1] = latt[1] * sing
aij[1][2] = 0.0;
aij[2][0] = latt[2] * cosb
aij[2][1] = (latt[2] * cosb - aij[2][0] * cosg)
if abs(aij[2][1]) < 1.0e-8:
aij[2][1] = 0.0
else:
aij[2][1] = aij[2][1] / sing
aij[2][2] = sqrt(latt[2] * latt[2] - aij[2][0] * aij[2][0] - aij[2][1] * aij[2][1])
return aij
def cal_metrics(latt):
inf = {}
cosa = cos(torad * latt[3])
cosb = cos(torad * latt[4])
cosg = cos(torad * latt[5])
aij = cal_lattice_vectors(latt)
inf['aij'] = aij
gij = np.empty([3, 3], dtype = float)
for i in range(3):
for j in range(i, 3):
gij[i][j] = np.dot(aij[i], aij[j])
gij[j][i] = gij[i][j]
inf['gij'] = gij
return inf
def cal_volume(aij):
axb = np.cross(aij[0], aij[1]) # Outner product
vol = np.dot(axb, aij[2]) # Inner product
return vol
def cal_reciprocal_lattice_vectors(aij):
V = cal_volume(aij)
Ra = np.cross(aij[1], aij[2]) / V
Rb = np.cross(aij[2], aij[0]) / V
Rc = np.cross(aij[0], aij[1]) / V
return [Ra, Rb, Rc]
def cal_reciprocal_lattice_parameters(Raij):
Ra = la.norm(Raij[0])
Rb = la.norm(Raij[1])
Rc = la.norm(Raij[2])
Ralpha = todeg * arccos(np.dot(Raij[1], Raij[2]) / Rb / Rc)
Rbeta = todeg * arccos(np.dot(Raij[2], Raij[0]) / Rc / Ra)
Rgamma = todeg * arccos(np.dot(Raij[0], Raij[1]) / Ra / Rb)
return [Ra, Rb, Rc, Ralpha, Rbeta, Rgamma]
def get_conversion_matrix_from_tij(tij, mode = 'x2xc'):
if mode == 'a2ac':
return tij
if mode == 'x2xc':
return np.linalg.inv(tij).transpose()
if mode == 'xc2x':
return tij.transpose()
if mode == 'h2hc':
return tij
if mode == 'a*2a*c':
return np.linalg.inv(tij).transpose()
if mode == 'ac*2a*':
return tij.transpose()
print("")
print("Error in tkcrystalbase.get_conversion_matrix: Invalid mode [{}]".format(mode))
print("")
exit()
def convert_lattice_vectors(aij, tij):
return tij @ aij
def cal_lattice_parameters_from_lattice_vectors(aij):
a = np.linalg.norm(aij[0])
b = np.linalg.norm(aij[1])
c = np.linalg.norm(aij[2])
alpha = np.dot(aij[1], aij[2]) / b / c * todeg
beta = np.dot(aij[2], aij[0]) / c / a * todeg
gamma = np.dot(aij[0], aij[1]) / a / b * todeg
return a, b, c, alpha, beta, gamma
def convert_fractional_coordinates_by_tij(pos, tij):
tijc = get_conversion_matrix_from_tij(tij, 'x2xc')
return tijc @ pos
def fractional_to_cartesian(pos, aij):
x = pos[0] * aij[0][0] + pos[1] * aij[1][0] + pos[2] * aij[2][0]
y = pos[0] * aij[0][1] + pos[1] * aij[1][1] + pos[2] * aij[2][1]
z = pos[0] * aij[0][2] + pos[1] * aij[1][2] + pos[2] * aij[2][2]
return x, y, z
def distance2(pos0, pos1, gij):
# dx = pos1 - pos0
dx = [pos1[0] - pos0[0], pos1[1] - pos0[1], pos1[2] - pos0[2]]
r2 = gij[0][0] * dx[0]*dx[0] + gij[1][1] * dx[1]*dx[1] + gij[2][2] * dx[2]*dx[2] \
+ 2.0 * (gij[0][1] * dx[0]*dx[1] + gij[0][2] * dx[0]*dx[2] + gij[1][2] * dx[1]*dx[2])
return r2
def distance(pos0, pos1, gij):
dx = pos1 - pos0
# dx = [pos1[0] - pos0[0], pos1[1] - pos0[1], pos1[2] - pos0[2]]
r2 = gij[0][0] * dx[0]*dx[0] + gij[1][1] * dx[1]*dx[1] + gij[2][2] * dx[2]*dx[2] \
+ 2.0 * (gij[0][1] * dx[0]*dx[1] + gij[0][2] * dx[0]*dx[2] + gij[1][2] * dx[1]*dx[2])
r = sqrt(r2)
return r
def add_site(sites, newsite, gij, rmin = 0.1):
rmin2 = rmin * rmin
name, label, z, M, q, r, color, pos = newsite
for s in sites:
name2, label2, z2, M2, q2, r2, color2, pos2 = s
dis2 = distance2(pos2, pos, gij)
if dis2 < rmin2:
return False
sites.append(newsite)
return True
def angle(pos0, pos1, pos2, gij):
dis01 = distance(pos0, pos1, gij)
if dis01 == 0.0:
return 0.0
dis02 = distance(pos0, pos2, gij)
if dis02 == 0.0:
return 0.0
dx01 = pos1 - pos0
dx02 = pos2 - pos0
# dx01 = [pos1[0] - pos0[0], pos1[1] - pos0[1], pos1[2] - pos0[2]]
# dx02 = [pos2[0] - pos0[0], pos2[1] - pos0[1], pos2[2] - pos0[2]]
ip = gij[0][0] * dx01[0]*dx02[0] + gij[1][1] * dx01[1]*dx02[1] + gij[2][2] * dx01[2]*dx02[2] \
+ 2.0 * (gij[0][1] * dx01[0]*dx02[1] + gij[0][2] * dx01[0]*dx02[2] + gij[1][2] * dx01[1]*dx02[2])
cosa = ip / dis01 / dis02
angle = todeg * arccos(cosa)
if angle > 180.0:
angle = 360.0 - angle
return angle;
def main():
print("")
print("Lattice parameters:", lattice_parameters)
aij = cal_lattice_vectors(lattice_parameters)
print("Lattice vectors:")
print(" ax: ({:10.4g}, {:10.4g}, {:10.4g}) A".format(aij[0][0], aij[0][1], aij[0][2]))
print(" ay: ({:10.4g}, {:10.4g}, {:10.4g}) A".format(aij[1][0], aij[1][1], aij[1][2]))
print(" az: ({:10.4g}, {:10.4g}, {:10.4g}) A".format(aij[2][0], aij[2][1], aij[2][2]))
inf = cal_metrics(lattice_parameters)
gij = inf['gij']
print("Metric tensor:")
print(" gij: ({:10.4g}, {:10.4g}, {:10.4g}) A".format(*gij[0]))
print(" ({:10.4g}, {:10.4g}, {:10.4g}) A".format(*gij[1]))
print(" ({:10.4g}, {:10.4g}, {:10.4g}) A".format(*gij[2]))
print("")
volume = cal_volume(aij)
print("Volume: {:12.4g} A^3".format(volume))
print("")
print("Unit cell volume: {:12.4g} A^3".format(volume))
Raij = cal_reciprocal_lattice_vectors(aij)
Rlatt = cal_reciprocal_lattice_parameters(Raij)
Rinf = cal_metrics(Rlatt)
Rgij = Rinf['gij']
print("Reciprocal lattice parameters:", Rlatt)
print("Reciprocal lattice vectors:")
print(" Rax: ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Raij[0]))
print(" Ray: ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Raij[1]))
print(" Raz: ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Raij[2]))
print("Reciprocal lattice metric tensor:")
print(" Rgij: ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Rgij[0]))
print(" ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Rgij[1]))
print(" ({:10.4g}, {:10.4g}, {:10.4g}) A^-1".format(*Rgij[2]))
Rvolume = cal_volume(Raij)
print("Reciprocal unit cell volume: {:12.4g} A^-3".format(Rvolume))
print("")
print("dis=", distance(np.array([0,0,0]), np.array([1,1,1]), gij))
print("angle=", angle (np.array([0,0,0]), np.array([1,1,1]), np.array([1,0,0]), gij))
print("angle=", angle (np.array([0,0,0]), np.array([1,0,0]), np.array([0,1,0]), gij))
print("")
exit()
if __name__ == '__main__':
main()