-
Notifications
You must be signed in to change notification settings - Fork 15
/
CijUtil.py
executable file
·302 lines (255 loc) · 13.1 KB
/
CijUtil.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/usr/bin/env python
# encoding: utf-8
"""
CijUtil.py
Bits to help with elastic constants manipulation.
polyCij(Cij): Given an elastic constants matrix for a single crystal,
calculate the Voight and Reuss bounds on the bulk and
shear moduli for a random polycrystal.
Copyright (c) 2010 Andrew Walker. All rights reserved.
"""
from __future__ import print_function
import numpy as np
version = 0.1
def latexCij(Cij, eCij, outputfile, nt=False):
"""Write out elastic constants and derived properties in a format
that can be processed as a LaTeX table."""
f = open(outputfile,"w")
for i in range(6):
for j in range(i,6):
if ((Cij[i,j] != 0.0) and (eCij[i,j] != 0.0)):
if (nt):
f.write("& {0:5.1f}$\pm${1:3.1f} \n".
format(Cij[i,j],eCij[i,j]))
else:
f.write("c$_{{{0}{1}}}$ & {2:5.1f}$\pm${3:3.1f} \\\\ \n".
format(i+1,j+1,Cij[i,j],eCij[i,j]))
if (nt):
f.write(" & \n")
else:
f.write(" & \\\\ \n")
(vB, rB, vG, rG, hB, hG, evB, erB, evG, erG, ehB, ehG) = polyCij(Cij, eCij)
(Ua, eUa) = uAniso(Cij, eCij)
if (nt):
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(vB, evB))
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(rB, erB))
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(hB, ehB))
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(vG, evG))
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(rG, erG))
f.write("& {0:5.1f}$\pm${1:3.1f} \n".format(hG, ehG))
f.write("& \n")
f.write("& {0:5.2f}$\pm${1:4.2f} \n".format(Ua, eUa))
else:
f.write("B$^v$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(vB, evB))
f.write("B$^r$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(rB, erB))
f.write("B$^{{vrh}}$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(hB, ehB))
f.write("G$^v$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(vG, evG))
f.write("G$^r$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(rG, erG))
f.write("G$^{{vrh}}$ & {0:5.1f}$\pm${1:3.1f} \\\\ \n".format(hG, ehG))
f.write(" & \\\\ \n")
f.write("A$_U$ & {0:5.2f}$\pm${1:4.2f} \\\\ \n".format(Ua, eUa))
f.close()
return None
def txtCij(Cij, filename):
"""Add elastic constants to a text file as a single line
(e.g. for bulk plotting). Order of elastic constants is:
C11 C12 C13 ... C16 C22 ... C26 C33 ... C55 C56 C66"""
f = open(filename, "a")
for i in range(6):
for j in range(i,6):
f.write("{0:5.1f} ".format(Cij[i,j]))
f.write("\n")
def CijStability(Cij):
"""Check that the elastic constants matrix is positive
definite - i.e. that the structure is stable to small
strains. This is done by finding the eigenvalues by
diagonalization and checking that they are all positive.
See Born & Huang, "Dynamical Theory of Crystal Lattices"
(1954) page 141."""
stable = False
(eigenvalues, eigenvectors) = np.linalg.eig(Cij)
if (np.amin(eigenvalues) > 0.0):
stable = True
else:
print("Crystal not stable to small strains")
print("(Cij not positive definite)")
print("Eigenvalues: " + str(eigenvalues))
return stable
def invertCij(Cij, eCij):
"""Given a square matrix and a square matrix of the errors
on each element, return the inverse of the matrix and the
propogated errors on the inverse.
We use numpy for the inversion and eq.10 of Lefebvre,
Keeler, Sobie and White ('Propagation of errors for
matrix inversion' Nuclear Instruments and Methods in
Physics Research A 451 pp.520-528; 2000) to calculate
the errors. The errors can be reported directly as the
errors on the inverse matrix but to do useful further
propogation we need to report the covar matrix too.
This is calculated from eq.9 and we then extract the
diagonal elements to get the errors (rather than implementing
eq.10 too).
Tested with the matrix:
0.700(7) 0.200(2)
0.400(4) 0.600(6)
which gives back the inverse and squared errors reported
in Table 1 of the above reference.
This is coded up for an elastic constants matrix (Cij) and
its inverse (the elastic compliance matrix, Sij), but should
work for any rank 2 square matrix.
"""
# Assuming we have a rank 2 square array
# of the same size for input array.
if (np.ndim(Cij) != 2):
raise ValueError("Matrix must be rank 2")
if (np.shape(Cij)[0] != np.shape(Cij)[1]):
raise ValueError("Matrix must be square")
if (np.shape(Cij) != np.shape(eCij)):
raise ValueError("Matrix and error matrix must have same rank and shape")
# Calculate the inverse using numpy
Sij = np.linalg.inv(Cij)
# Set up output arrays (init as zeros)
eSij = np.zeros_like(eCij)
array_size = eSij[0].size
vcovSij = np.zeros((array_size,array_size,array_size,array_size),dtype=type(eSij))
# Build covariance arrays (i.e COV(C^-1[a,b],S^-1[b,c] - a 4d array).
# This is an implementation of eq.9 of Lefebvre et al.
for a in range (array_size):
for b in range (array_size):
for c in range (array_size):
for d in range (array_size):
for i in range (array_size):
for j in range (array_size):
vcovSij[a,b,c,d] = vcovSij[a,b,c,d] + \
((Sij[a,i]*Sij[c,i]) * (eCij[i,j]**2) * (Sij[j,b]*Sij[j,d]))
# Extrct the "diagonal" terms, which are
# the errors on the elements of the inverse
# and could also be calculated using eq.10
for a in range (array_size):
for b in range (array_size):
eSij[a,b] = np.sqrt(vcovSij[a,b,a,b])
return (Sij, eSij, vcovSij)
def polyCij(Cij, eCij=np.zeros((6,6))):
"""Returns voight-reuss-hill average of elastic constants tensor
and propogated error given the 6*6 matrix of elastic constants
and the 6*6 matrix of errors. The errors are optional. Assumes
no co-varance between the errors on the elastic constants but
does include the co-varenance on the (calculated) compliance
matrix."""
# Need compliances too:
(sij, eSij, covSij) = invertCij(Cij, eCij)
# These equations are valid for all crystal systems (only 9 of
# the 21 elastic constants ever have to be used, e.g. see Anderson
# theory of the Earth, pg. 122 or the introduction to Hill, 1952).
voigtB = (1.0/9)*(Cij[0,0] + Cij[1,1] + Cij[2,2] ) \
+ (2.0/9)*(Cij[0,1] + Cij[0,2] + Cij[1,2])
evB = np.sqrt( (1.0/81)*(eCij[0,0]**2 + eCij[1,1]**2 + eCij[2,2]**2) \
+(2.0/81)*(eCij[0,1]**2 + eCij[0,2]**2 + eCij[1,2]**2) )
reussB = 1.0/((sij[0,0]+sij[1,1]+sij[2,2]) + 2*(sij[0,1]+sij[0,2]+sij[1,2]))
# Note that COV(X+Z,Y) = COV(X,Y)+COV(Z,Y) and
# COV(SUM(Xi),SUM(Yj)) = SUM(SUM(COV(Xi,Yj)
# c.f. http://mathworld.wolfram.com/Covariance.html
erB = (np.sqrt(eSij[0,0]**2 + eSij[1,1]**2 + eSij[2,2]**2 \
+ 4*eSij[0,1]**2 + 4*eSij[0,2]**2 + 4*eSij[1,2]**2 \
+ 2*covSij[0,0,1,1] + 2*covSij[0,0,2,2] + 2*covSij[1,1,2,2] \
+ 4*covSij[0,0,0,1] + 4*covSij[0,0,0,2] + 4*covSij[0,0,1,2] \
+ 4*covSij[1,1,0,1] + 4*covSij[1,1,0,2] + 4*covSij[1,1,1,2] \
+ 4*covSij[2,2,0,1] + 4*covSij[2,2,0,2] + 4*covSij[2,2,1,2] \
+ 8*covSij[0,1,0,2] + 8*covSij[0,1,1,2] + 8*covSij[0,2,1,2] )) \
* reussB**2
voigtG = (1.0/15)*(Cij[0,0] + Cij[1,1] + Cij[2,2] - \
Cij[0,1] - Cij[0,2] - Cij[1,2]) + \
(1.0/5)*(Cij[3,3] + Cij[4,4] + Cij[5,5])
evG = np.sqrt( (1.0/225)*(eCij[0,0]**2 + eCij[1,1]**2 + \
eCij[2,2]**2 + eCij[0,1]**2 + \
eCij[0,2]**2 + eCij[1,2]**2) + \
(1.0/25)*(eCij[3,3]**2 + eCij[4,4]**2 + eCij[5,5]**2) )
reussG = 15.0/(4*(sij[0,0]+sij[1,1]+sij[2,2]) - \
4*(sij[0,1]+sij[0,2]+sij[1,2]) + 3*(sij[3,3]+sij[4,4]+sij[5,5]))
erG = np.sqrt( \
16*(eSij[0,0]**2 + eSij[1,1]**2 + eSij[2,2]**2) \
+ 16*(eSij[0,1]**2 + eSij[0,2]**2 + eSij[1,2]**2) \
+ 9*(eSij[3,3]**2 + eSij[4,4]**2 + eSij[5,5]**2) \
+ 32*covSij[0,0,1,1] + 32*covSij[0,0,2,2] + 32*covSij[1,1,2,2] \
+ 32*covSij[0,0,0,1] + 32*covSij[0,0,0,2] + 32*covSij[0,0,1,2] \
+ 32*covSij[1,1,0,1] + 32*covSij[1,1,0,2] + 32*covSij[1,1,1,2] \
+ 32*covSij[2,2,0,1] + 32*covSij[2,2,0,2] + 32*covSij[2,2,1,2] \
+ 32*covSij[0,1,0,2] + 32*covSij[0,1,1,2] + 32*covSij[0,2,1,2] \
+ 24*covSij[0,0,3,3] + 24*covSij[0,0,4,4] + 24*covSij[0,0,5,5] \
+ 24*covSij[1,1,3,3] + 24*covSij[1,1,4,4] + 24*covSij[1,1,5,5] \
+ 24*covSij[2,2,3,3] + 24*covSij[2,2,4,4] + 24*covSij[2,2,5,5] \
+ 24*covSij[0,1,3,3] + 24*covSij[0,1,4,4] + 24*covSij[0,1,5,5] \
+ 24*covSij[0,2,3,3] + 24*covSij[0,2,4,4] + 24*covSij[0,2,5,5] \
+ 24*covSij[1,2,3,3] + 24*covSij[1,2,4,4] + 24*covSij[1,2,5,5] \
+ 18*covSij[3,3,4,4] + 18*covSij[3,3,5,5] + 18*covSij[4,4,5,5] \
) * (reussG**2 / 15)
return (voigtB, reussB, voigtG, reussG, ((voigtB+reussB)/2.0), ((voigtG+reussG)/2.0),
evB, erB, evG, erG, ((evB+erB)/2), ((evG+erG)/2))
def zenerAniso(Cij,eCij=np.zeros((6,6))):
"""Returns Zener anisotropy index, A, defined as
2C44/(C11-C12). This is unity for an isotropic crystal
and, for a cubic crystal C44 and 1/2(C11-C12) are shear
strains accross the (100) and (110) planes, respectivly.
See Zener, Elasticity and Anelasticity of Metals, 1948
or doi:10.1103/PhysRevLett.101.055504 (c.f. uAniso).
Also returns the error on the anisotriopy index.
Note that we don't check that the crystal is cubic!"""
zA = (Cij[3,3]*2)/(Cij[0,0]-Cij[0,1])
ezA = np.sqrt(((eCij[0,0]/Cij[0,0])**2 + (eCij[0,1]/Cij[0,1])**2) +\
(2*(eCij[3,3]/Cij[3,3])**2)) * zA
return (zA, ezA)
def uAniso(Cij,eCij):
"""Returns the Universal elastic anisotropy index defined
by Ranganathan and Ostoja-Starzewski (PRL 101, 05504; 2008
doi:10.1103/PhysRevLett.101.055504 ). Valid for all systems."""
(voigtB, reussB, voigtG, reussG, hillB, hillG,
evB, erB, evG, erG, ehB, ehG) = polyCij(Cij,eCij)
uA = (5*(voigtG/reussG))+(voigtB/reussB)-6
euA = np.sqrt((np.sqrt((evG/voigtG)**2 + (erG/reussG)**2)*(voigtG/reussG))**2 + \
(np.sqrt((evB/voigtB)**2 + (erB/reussB)**2)*(voigtB/reussB))**2)
return (uA, euA)
def youngsmod(Cij, eCij=np.zeros((6,6))):
"""Returns the Young's moduli, Poission ratio
and errors. Young's moduli is the ratio of tensile
stress to tensile strain. Poission's ratios are ratio
of tensile elongation and transverse contraction. """
(sij, esij, covsij) = invertCij(Cij, eCij)
youngX = 1/sij[0,0]
youngY = 1/sij[1,1]
youngZ = 1/sij[2,2]
eyoungX = (esij[0,0]/sij[0,0])*youngX
eyoungY = (esij[1,1]/sij[1,1])*youngY
eyoungZ = (esij[2,2]/sij[2,2])*youngZ
poissonXY = -1*sij[0,1]*youngX
poissonXZ = -1*sij[0,2]*youngX
poissonYX = -1*sij[1,0]*youngY
poissonYZ = -1*sij[1,2]*youngY
poissonZX = -1*sij[2,0]*youngZ
poissonZY = -1*sij[2,1]*youngZ
epoissonXY = np.sqrt((esij[0,1]/sij[0,1])**2 + (esij[0,0]/sij[0,0])**2 -
2.0*((esij[0,1]*esij[0,0])/(sij[0,1]*sij[0,0]))*covsij[0,1,0,0])*poissonXY
epoissonXZ = np.sqrt((esij[0,2]/sij[0,2])**2 + (esij[0,0]/sij[0,0])**2 -
2.0*((esij[0,2]*esij[0,0])/(sij[0,2]*sij[0,0]))*covsij[0,2,0,0])*poissonXZ
epoissonYX = np.sqrt((esij[1,0]/sij[1,0])**2 + (esij[1,1]/sij[1,1])**2 -
2.0*((esij[1,0]*esij[1,1])/(sij[1,0]*sij[1,1]))*covsij[1,0,1,1])*poissonYX
epoissonYZ = np.sqrt((esij[1,2]/sij[1,2])**2 + (esij[1,1]/sij[1,1])**2 -
2.0*((esij[1,2]*esij[1,1])/(sij[1,2]*sij[1,1]))*covsij[1,2,1,1])*poissonYZ
epoissonZX = np.sqrt((esij[2,0]/sij[2,0])**2 + (esij[2,2]/sij[2,2])**2 -
2.0*((esij[2,0]*esij[2,2])/(sij[2,0]*sij[2,2]))*covsij[2,0,2,2])*poissonZX
epoissonZY = np.sqrt((esij[2,1]/sij[2,1])**2 + (esij[2,2]/sij[2,2])**2 -
2.0*((esij[2,1]*esij[2,2])/(sij[2,1]*sij[2,2]))*covsij[2,1,2,2])*poissonZY
return (youngX, youngY, youngZ, eyoungX, eyoungY, eyoungZ,
poissonXY, poissonXZ, poissonYX, poissonYZ, poissonZX, poissonZY,
epoissonXY, epoissonXZ, epoissonYX, epoissonYZ, epoissonZX, epoissonZY)
if __name__ == '__main__':
import sys
inFile = file(sys.argv[1], 'r')
Cij_in = np.loadtxt(inFile)
print("Input matrix:")
print (np.array2string(Cij_in,max_line_width=130,suppress_small=True))
(voigtB, reussB, voigtG, reussG, hillB, hillG,
dum, dum, dum, dum, dum, dum) = polyCij(Cij_in)
format = "%16s : %11.5f %11.5f %11.5f"
print("\n Voigt Reuss Hill")
print(format % ("Bulk Modulus", voigtB, reussB, hillB))
print(format % ("Shear Modulus", voigtG, reussG, hillG))