Appendix: Calculating the Strain

Metric Tensor

\[\begin{split}M=\begin{pmatrix}\vec a.\vec a&\vec a.\vec b&\vec a.\vec c\\ \vec b.\vec a&\vec b.\vec b&\vec b.\vec c\\ \vec c.\vec a&\vec c.\vec b&\vec c.\vec c\end{pmatrix}\end{split}\]

Standard Root Tensor

\[M=R^T R\]

Finite Lagrangian Strain Tensor

\[L=\frac{1}{2}(e+e^T+e^T e),\quad e=R_2 R_1^{-1} - I\]

Calculate the eigenvalues \(\{\eta_i\}\) of the finite Lagrangian strain tensor, and the “degree of lattice distortion” is defined as the square root of the sum of squared eigenvalues of strain tensor divided by the 3.

Strain

\[S=\frac{1}{3}\sqrt{\sum_i{\eta_i^2}}\]
import numpy as np
def par2vec(par):
    # Converts from parametric form to vectorial form
    # Lattice parameters are input as a 6 dimensional vector
    # Returns the lattice vectors as a 3x3 matrix, with each
    #   vector as a row-vector
    a = par[0]
    b = par[1]
    c = par[2]
    alpha = np.deg2rad(par[3])
    beta  = np.deg2rad(par[4])
    gamma = np.deg2rad(par[5])
    
    a1 = a
    vec_a = np.array([a1,0,0])
    
    b1 = b*np.cos(gamma)
    b2 = np.sqrt(b**2 - b1**2)
    vec_b = np.array([b1,b2,0])
    
    c1 = c*np.cos(beta)
    c2 = ((b*c*np.cos(alpha))- b1*c1)/b2
    c3 = np.sqrt(c**2 - c1**2 - c2**2)
    vec_c = np.array([c1,c2,c3])
    
    abc_vec = np.array([vec_a,vec_b,vec_c])
    return abc_vec

def vec2par(vec):
    # Converts from vectorial form to parametric form
    # Vectors are input as a 3x3, with each lattice
    #   as a row-vector
    # Returns the lattice parameters as a 
    #   6 dimensional vector
    
    vec_a = vec[0,:]
    vec_b = vec[1,:]
    vec_c = vec[2,:]
    
    a = np.linalg.norm(vec_a)
    b = np.linalg.norm(vec_b)
    c = np.linalg.norm(vec_c)
    
    alpha = np.rad2deg(np.arccos(np.dot(vec_b,vec_c)/(b*c)))
    beta  = np.rad2deg(np.arccos(np.dot(vec_a,vec_c)/(a*c)))
    gamma = np.rad2deg(np.arccos(np.dot(vec_a,vec_b)/(a*b)))
    
    par = np.array([a,b,c,alpha,beta,gamma])
    return par

def metric_from_vector(vec):
    # Constructs the metric tensor from the input lattice vectors
    # Vectors are input as a 3x3, with each lattice vector given
    #   as a row-vector
    # Returns the metric tensor as a 3x3 matrix
      
    G = np.empty((3,3))
    for i in range(3):
        for j in range(i,3):
            G[i,j] = G[j,i] = np.dot(vec[i,:],vec[j,:])
            
    return G

def parameters_from_metric(G):
    a = np.sqrt(G[0,0])
    b = np.sqrt(G[1,1])
    c = np.sqrt(G[2,2])
    
    alpha = np.rad2deg(np.arccos(G[1,2]/(b*c)))
    beta = np.rad2deg(np.arccos(G[0,2]/(a*c)))
    gamma = np.rad2deg(np.arccos(G[0,1]/(a*b)))
    
    return np.array([a,b,c,alpha,beta,gamma])

Warning

When calculating the strain, the order of the two lattices matters! Below are two calculations with the order swapped.

par_FeS2_029 = np.array([5.4178, 5.4178, 5.4178, 90.0000, 90.0000, 90.0000])
par_PtGeSe_029 = np.array([6.015, 6.072, 5.992, 90., 90., 90.])

par_1 = par_FeS2_029
par_2 = par_PtGeSe_029

vec_1 = par2vec(par_1)
vec_2 = par2vec(par_2)

G_1 = metric_from_vector(vec_1)
G_2 = metric_from_vector(vec_2)

R_1 = np.linalg.cholesky(G_1)
R_2 = np.linalg.cholesky(G_2)

e = np.dot(R_2,np.linalg.inv(R_1)) - np.eye(3,3)
L = 0.5*(e+e.T+np.dot(e.T,e))

[l,u] = np.linalg.eig(L)

S = np.linalg.norm(l)/3
print(S)
0.06861786655908578
par_2 = par_FeS2_029
par_1 = par_PtGeSe_029

vec_1 = par2vec(par_1)
vec_2 = par2vec(par_2)

G_1 = metric_from_vector(vec_1)
G_2 = metric_from_vector(vec_2)

R_1 = np.linalg.cholesky(G_1)
R_2 = np.linalg.cholesky(G_2)

e = np.dot(R_2,np.linalg.inv(R_1)) - np.eye(3,3)
L = 0.5*(e+e.T+np.dot(e.T,e))

[l,u] = np.linalg.eig(L)

S = np.linalg.norm(l)/3
print(S)
0.05539576887115969