elasticipy 2.9.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Elasticipy/FourthOrderTensor.py +360 -44
- Elasticipy/Plasticity.py +105 -55
- Elasticipy/SecondOrderTensor.py +243 -54
- Elasticipy/StressStrainTensors.py +9 -5
- Elasticipy/ThermalExpansion.py +88 -17
- {elasticipy-2.9.0.dist-info → elasticipy-3.0.0.dist-info}/METADATA +2 -1
- elasticipy-3.0.0.dist-info/RECORD +15 -0
- {elasticipy-2.9.0.dist-info → elasticipy-3.0.0.dist-info}/WHEEL +1 -1
- elasticipy-2.9.0.dist-info/RECORD +0 -15
- {elasticipy-2.9.0.dist-info → elasticipy-3.0.0.dist-info}/LICENSE +0 -0
- {elasticipy-2.9.0.dist-info → elasticipy-3.0.0.dist-info}/top_level.txt +0 -0
Elasticipy/FourthOrderTensor.py
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
|
-
from Elasticipy.SecondOrderTensor import SymmetricSecondOrderTensor, rotation_to_matrix, is_orix_rotation
|
|
4
|
+
from Elasticipy.SecondOrderTensor import SymmetricSecondOrderTensor, rotation_to_matrix, is_orix_rotation, \
|
|
5
|
+
SecondOrderTensor, ALPHABET
|
|
6
|
+
from Elasticipy.SecondOrderTensor import _orientation_shape, _is_single_rotation
|
|
5
7
|
from Elasticipy.StressStrainTensors import StrainTensor, StressTensor
|
|
6
8
|
from Elasticipy.SphericalFunction import SphericalFunction, HyperSphericalFunction
|
|
7
9
|
from scipy.spatial.transform import Rotation
|
|
8
10
|
from Elasticipy.CrystalSymmetries import SYMMETRIES
|
|
9
11
|
from copy import deepcopy
|
|
10
12
|
|
|
13
|
+
a = np.sqrt(2)
|
|
14
|
+
_voigt_to_kelvin_matrix = np.array([[1, 1, 1, a, a, a],
|
|
15
|
+
[1, 1, 1, a, a, a],
|
|
16
|
+
[1, 1, 1, a, a, a],
|
|
17
|
+
[a, a, a, 2, 2, 2],
|
|
18
|
+
[a, a, a, 2, 2, 2],
|
|
19
|
+
[a, a, a, 2, 2, 2],])
|
|
20
|
+
|
|
21
|
+
|
|
11
22
|
def _parse_tensor_components(prefix, **kwargs):
|
|
12
23
|
pattern = r'^{}(\d{{2}})$'.format(prefix)
|
|
13
24
|
value = dict()
|
|
@@ -67,7 +78,7 @@ def _compute_unit_strain_along_direction(S, m, n, direction='longitudinal'):
|
|
|
67
78
|
if direction == 'transverse':
|
|
68
79
|
ein_str = 'ijkl,pi,pj,pk,pl->p'
|
|
69
80
|
return np.einsum(ein_str, S.full_tensor(), m, m, n, n)
|
|
70
|
-
elif direction =='longitudinal':
|
|
81
|
+
elif direction == 'longitudinal':
|
|
71
82
|
ein_str = 'ijkl,pi,pk,pj,pl->p'
|
|
72
83
|
return np.einsum(ein_str, S.full_tensor(), m, m, n, n)
|
|
73
84
|
else:
|
|
@@ -83,6 +94,7 @@ def _isotropic_matrix(C11, C12, C44):
|
|
|
83
94
|
[0, 0, 0, 0, C44, 0],
|
|
84
95
|
[0, 0, 0, 0, 0, C44]])
|
|
85
96
|
|
|
97
|
+
|
|
86
98
|
def _check_definite_positive(mat):
|
|
87
99
|
try:
|
|
88
100
|
np.linalg.cholesky(mat)
|
|
@@ -91,20 +103,12 @@ def _check_definite_positive(mat):
|
|
|
91
103
|
raise ValueError('The input matrix is not definite positive (eigenvalues: {})'.format(eigen_val))
|
|
92
104
|
|
|
93
105
|
|
|
94
|
-
def _is_single_rotation(rotation):
|
|
95
|
-
if isinstance(rotation, Rotation):
|
|
96
|
-
return rotation.single
|
|
97
|
-
elif is_orix_rotation(rotation):
|
|
98
|
-
return rotation.size == 1
|
|
99
|
-
else:
|
|
100
|
-
raise TypeError('The input argument must be of class scipy.transform.Rotation or '
|
|
101
|
-
'orix.quaternion.rotation.Rotation')
|
|
102
|
-
|
|
103
106
|
def _rotate_tensor(full_tensor, rotation):
|
|
104
107
|
rot_mat = rotation_to_matrix(rotation)
|
|
105
|
-
str_ein =
|
|
108
|
+
str_ein = '...im,...jn,...ko,...lp,mnop->...ijkl'
|
|
106
109
|
return np.einsum(str_ein, rot_mat, rot_mat, rot_mat, rot_mat, full_tensor)
|
|
107
110
|
|
|
111
|
+
|
|
108
112
|
class SymmetricTensor:
|
|
109
113
|
"""
|
|
110
114
|
Template class for manipulating symmetric fourth-order tensors.
|
|
@@ -145,9 +149,9 @@ class SymmetricTensor:
|
|
|
145
149
|
Whether to check or not that the input matrix is definite positive
|
|
146
150
|
"""
|
|
147
151
|
M = np.asarray(M)
|
|
148
|
-
if M.shape == (6,6):
|
|
152
|
+
if M.shape == (6, 6):
|
|
149
153
|
matrix = M
|
|
150
|
-
elif M.shape == (3,3,3,3):
|
|
154
|
+
elif M.shape == (3, 3, 3, 3):
|
|
151
155
|
matrix = self._full_to_matrix(M)
|
|
152
156
|
else:
|
|
153
157
|
raise ValueError('The input matrix must of shape (6,6)')
|
|
@@ -161,12 +165,13 @@ class SymmetricTensor:
|
|
|
161
165
|
self.symmetry = symmetry
|
|
162
166
|
self.orientations = orientations
|
|
163
167
|
|
|
164
|
-
for i in range(0,6):
|
|
165
|
-
for j in range(0,6):
|
|
168
|
+
for i in range(0, 6):
|
|
169
|
+
for j in range(0, 6):
|
|
166
170
|
def getter(obj, I=i, J=j):
|
|
167
171
|
return obj.matrix[I, J]
|
|
172
|
+
|
|
168
173
|
getter.__doc__ = f"Returns the ({i + 1},{j + 1}) component of the {self.tensor_name} matrix."
|
|
169
|
-
component_name = 'C{}{}'.format(i+1, j+1)
|
|
174
|
+
component_name = 'C{}{}'.format(i + 1, j + 1)
|
|
170
175
|
setattr(self.__class__, component_name, property(getter)) # Dynamically create the property
|
|
171
176
|
|
|
172
177
|
def __repr__(self):
|
|
@@ -177,14 +182,37 @@ class SymmetricTensor:
|
|
|
177
182
|
print_symmetry = '\nSymmetry: {}'.format(self.symmetry)
|
|
178
183
|
msg = heading + self.matrix.__str__() + print_symmetry
|
|
179
184
|
if self.orientations is not None:
|
|
180
|
-
|
|
185
|
+
shape = self.shape
|
|
186
|
+
if len(shape) == 1:
|
|
187
|
+
msg = msg + '\n{} orientations'.format(shape[0])
|
|
188
|
+
else:
|
|
189
|
+
msg = msg + '\n{} orientations'.format(shape)
|
|
181
190
|
return msg
|
|
182
191
|
|
|
183
192
|
def __len__(self):
|
|
184
|
-
|
|
193
|
+
o = self.orientations
|
|
194
|
+
if o is None:
|
|
185
195
|
return 1
|
|
186
196
|
else:
|
|
187
|
-
|
|
197
|
+
if is_orix_rotation(o):
|
|
198
|
+
return o.shape[0]
|
|
199
|
+
else:
|
|
200
|
+
return len(o)
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def shape(self):
|
|
204
|
+
"""
|
|
205
|
+
Return the shape of the tensor array
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
tuple
|
|
209
|
+
Shape of the tensor array
|
|
210
|
+
"""
|
|
211
|
+
o = self.orientations
|
|
212
|
+
if o is None:
|
|
213
|
+
return None
|
|
214
|
+
else:
|
|
215
|
+
return _orientation_shape(o)
|
|
188
216
|
|
|
189
217
|
def full_tensor(self):
|
|
190
218
|
"""
|
|
@@ -204,6 +232,26 @@ class SymmetricTensor:
|
|
|
204
232
|
else:
|
|
205
233
|
return _rotate_tensor(m, self.orientations)
|
|
206
234
|
|
|
235
|
+
def flatten(self):
|
|
236
|
+
"""
|
|
237
|
+
Flatten the tensor
|
|
238
|
+
|
|
239
|
+
If the tensor has (m,n,o...,r) orientations, the flattened tensor will have m*n*o*...*r orientations
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
SymmetricTensor
|
|
244
|
+
Flattened tensor
|
|
245
|
+
"""
|
|
246
|
+
tensor_flat = self._unrotate()
|
|
247
|
+
o = self.orientations
|
|
248
|
+
if is_orix_rotation(o):
|
|
249
|
+
o_flat = o.flatten()
|
|
250
|
+
else:
|
|
251
|
+
o_flat = o
|
|
252
|
+
tensor_flat.orientations = o_flat
|
|
253
|
+
return tensor_flat
|
|
254
|
+
|
|
207
255
|
@classmethod
|
|
208
256
|
def _full_to_matrix(cls, full_tensor):
|
|
209
257
|
ij, kl = np.indices((6, 6))
|
|
@@ -232,6 +280,41 @@ class SymmetricTensor:
|
|
|
232
280
|
else:
|
|
233
281
|
raise ValueError('The rotation to apply must be single')
|
|
234
282
|
|
|
283
|
+
@property
|
|
284
|
+
def ndim(self):
|
|
285
|
+
"""
|
|
286
|
+
Returns the dimensionality of the tensor (number of dimensions in the orientation array)
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
int
|
|
291
|
+
Number of dimensions
|
|
292
|
+
"""
|
|
293
|
+
shape = self.shape
|
|
294
|
+
if shape:
|
|
295
|
+
return len(shape)
|
|
296
|
+
else:
|
|
297
|
+
return 0
|
|
298
|
+
|
|
299
|
+
def mean(self, axis=None):
|
|
300
|
+
"""
|
|
301
|
+
Compute the mean value of the tensor T, considering the orientations
|
|
302
|
+
|
|
303
|
+
Parameters
|
|
304
|
+
----------
|
|
305
|
+
axis : int or list of int or tuple of int, optional
|
|
306
|
+
axis along which to compute the mean. If None, the mean is computed on the flattened tensor
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
numpy.ndarray
|
|
311
|
+
If no axis is given, the result will be of shape (3,3,3,3).
|
|
312
|
+
Otherwise, if T.ndim=m, and len(axis)=n, the returned value will be of shape (...,3,3,3,3), with ndim=m-n+4
|
|
313
|
+
"""
|
|
314
|
+
if axis is None:
|
|
315
|
+
axis = tuple([i for i in range(self.ndim)])
|
|
316
|
+
return np.mean(self.full_tensor(), axis=axis)
|
|
317
|
+
|
|
235
318
|
def _unrotate(self):
|
|
236
319
|
unrotated_tensor = deepcopy(self)
|
|
237
320
|
unrotated_tensor.orientations = None
|
|
@@ -264,19 +347,54 @@ class SymmetricTensor:
|
|
|
264
347
|
if isinstance(other, SymmetricSecondOrderTensor):
|
|
265
348
|
return SymmetricSecondOrderTensor(self * other.matrix)
|
|
266
349
|
elif isinstance(other, np.ndarray):
|
|
267
|
-
if other.shape
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
350
|
+
if other.shape == (3, 3):
|
|
351
|
+
# other is a single tensor
|
|
352
|
+
matrix = np.einsum('...ijkl,kl->...ij', self.full_tensor(), other)
|
|
353
|
+
return SecondOrderTensor(matrix)
|
|
354
|
+
elif self.shape is None:
|
|
355
|
+
# other is an array, but self is single
|
|
356
|
+
matrix = np.einsum('ijkl,...kl->...ij', self.full_tensor(), other)
|
|
357
|
+
return SecondOrderTensor(matrix)
|
|
358
|
+
elif (self.ndim >= (other.ndim - 2)) and self.shape[-other.ndim+2:]==other.shape[:-2]:
|
|
359
|
+
# self.shape==(m,n,o,p) and other.shape==(o,p,3,3)
|
|
360
|
+
indices_0 = ALPHABET[:self.ndim]
|
|
361
|
+
indices_1 = indices_0[-other.ndim+2:]
|
|
362
|
+
ein_str = indices_0 + 'IJKL,' + indices_1 + 'KL->' + indices_0 + 'IJ'
|
|
363
|
+
matrix = np.einsum(ein_str, self.full_tensor(), other)
|
|
364
|
+
return SecondOrderTensor(matrix)
|
|
365
|
+
elif self.shape == other.shape[:-2]:
|
|
366
|
+
# other and self are arrays of the same shape
|
|
367
|
+
matrix = np.einsum('...ijkl,...kl->...ij', self.full_tensor(), other)
|
|
368
|
+
return SecondOrderTensor(matrix)
|
|
369
|
+
else:
|
|
370
|
+
raise ValueError('The arrays to multiply could not be broadcast with shapes {} and {}'.format(self.shape, other.shape[:-2]))
|
|
272
371
|
elif isinstance(other, Rotation) or is_orix_rotation(other):
|
|
273
372
|
if _is_single_rotation(other):
|
|
274
373
|
return self.rotate(other)
|
|
275
374
|
else:
|
|
276
|
-
return self.__class__(self.matrix, symmetry=self.symmetry, orientations=other,
|
|
375
|
+
return self.__class__(self.matrix, symmetry=self.symmetry, orientations=other,
|
|
376
|
+
phase_name=self.phase_name)
|
|
277
377
|
else:
|
|
278
378
|
return self.__class__(self.matrix * other, symmetry=self.symmetry)
|
|
279
379
|
|
|
380
|
+
def transpose_array(self):
|
|
381
|
+
"""
|
|
382
|
+
Transpose the orientations of the tensor array
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
FourthOrderTensor
|
|
387
|
+
The same tensor, but with transposed orientations
|
|
388
|
+
"""
|
|
389
|
+
ndim = self.ndim
|
|
390
|
+
if ndim==0 or ndim==1:
|
|
391
|
+
return self
|
|
392
|
+
else:
|
|
393
|
+
new_tensor = self._unrotate()
|
|
394
|
+
new_order = np.flip(range(ndim))
|
|
395
|
+
new_tensor.orientations = self.orientations.transpose(*new_order)
|
|
396
|
+
return new_tensor
|
|
397
|
+
|
|
280
398
|
def __rmul__(self, other):
|
|
281
399
|
if isinstance(other, (Rotation, float, int, np.number)) or is_orix_rotation(other):
|
|
282
400
|
return self * other
|
|
@@ -292,17 +410,24 @@ class SymmetricTensor:
|
|
|
292
410
|
def __eq__(self, other):
|
|
293
411
|
if isinstance(other, SymmetricTensor):
|
|
294
412
|
return np.all(self.matrix == other.matrix) and np.all(self.orientations == other.orientations)
|
|
295
|
-
elif isinstance(other, np.ndarray) and other.shape == (6,6):
|
|
413
|
+
elif isinstance(other, np.ndarray) and other.shape == (6, 6):
|
|
296
414
|
return np.all(self.matrix == other)
|
|
297
415
|
else:
|
|
298
416
|
raise NotImplementedError('The element to compare with must be a fourth-order tensor '
|
|
299
417
|
'or an array of shape (6,6).')
|
|
300
418
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
419
|
+
def matmul(self, other):
|
|
420
|
+
if isinstance(other, SecondOrderTensor):
|
|
421
|
+
other_matrix = other.matrix
|
|
422
|
+
else:
|
|
423
|
+
other_matrix = other
|
|
424
|
+
indices_0= 'abcdefgh'
|
|
425
|
+
indices_1= indices_0.upper()
|
|
426
|
+
indices_0 = indices_0[:self.ndim]
|
|
427
|
+
indices_1 = indices_1[:other_matrix.ndim-2]
|
|
428
|
+
ein_str = indices_0 + 'ijkl,' + indices_1 +'kl->' + indices_0 + indices_1 + 'ij'
|
|
429
|
+
new_mat = np.einsum(ein_str, self.full_tensor(), other_matrix)
|
|
430
|
+
return StrainTensor(new_mat)
|
|
306
431
|
|
|
307
432
|
@classmethod
|
|
308
433
|
def _matrixFromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', prefix=None, **kwargs):
|
|
@@ -724,7 +849,8 @@ class SymmetricTensor:
|
|
|
724
849
|
if self.orientations is None:
|
|
725
850
|
raise IndexError('The tensor has no orientation, therefore it cannot be indexed.')
|
|
726
851
|
else:
|
|
727
|
-
return self._unrotate()*self.orientations[item]
|
|
852
|
+
return self._unrotate() * self.orientations[item]
|
|
853
|
+
|
|
728
854
|
|
|
729
855
|
class StiffnessTensor(SymmetricTensor):
|
|
730
856
|
"""
|
|
@@ -823,6 +949,7 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
823
949
|
--------
|
|
824
950
|
bulk_modulus : bulk modulus of the material
|
|
825
951
|
"""
|
|
952
|
+
|
|
826
953
|
def compute_linear_compressibility(n):
|
|
827
954
|
return _compute_unit_strain_along_direction(self, n, n, direction='spherical')
|
|
828
955
|
|
|
@@ -844,7 +971,6 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
844
971
|
"""
|
|
845
972
|
return self.inv().bulk_modulus
|
|
846
973
|
|
|
847
|
-
|
|
848
974
|
def Voigt_average(self):
|
|
849
975
|
"""
|
|
850
976
|
Compute the Voigt average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
|
|
@@ -873,7 +999,7 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
873
999
|
mat = _isotropic_matrix(C11, C12, C44)
|
|
874
1000
|
return StiffnessTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
875
1001
|
else:
|
|
876
|
-
return self.
|
|
1002
|
+
return StiffnessTensor(self.mean())
|
|
877
1003
|
|
|
878
1004
|
def Reuss_average(self):
|
|
879
1005
|
"""
|
|
@@ -941,7 +1067,6 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
941
1067
|
else:
|
|
942
1068
|
raise NotImplementedError('Only Voigt, Reus, and Hill are implemented.')
|
|
943
1069
|
|
|
944
|
-
|
|
945
1070
|
@classmethod
|
|
946
1071
|
def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
|
|
947
1072
|
"""
|
|
@@ -1044,10 +1169,10 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
1044
1169
|
"""
|
|
1045
1170
|
tri_sup = np.array([[1 / Ex, -nu_yx / Ey, -nu_zx / Ez, 0, 0, 0],
|
|
1046
1171
|
[0, 1 / Ey, -nu_zy / Ez, 0, 0, 0],
|
|
1047
|
-
[0,
|
|
1048
|
-
[0,
|
|
1049
|
-
[0,
|
|
1050
|
-
[0,
|
|
1172
|
+
[0, 0, 1 / Ez, 0, 0, 0],
|
|
1173
|
+
[0, 0, 0, 1 / Gyz, 0, 0],
|
|
1174
|
+
[0, 0, 0, 0, 1 / Gxz, 0],
|
|
1175
|
+
[0, 0, 0, 0, 0, 1 / Gxy]])
|
|
1051
1176
|
S = tri_sup + np.tril(tri_sup.T, -1)
|
|
1052
1177
|
return StiffnessTensor(np.linalg.inv(S), symmetry='orthotropic', **kwargs)
|
|
1053
1178
|
|
|
@@ -1298,7 +1423,7 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
1298
1423
|
.. [3] S. I. Ranganathan and M. Ostoja-Starzewski, Universal Elastic Anisotropy Index,
|
|
1299
1424
|
*Phys. Rev. Lett.*, 101(5), 055504, 2008. https://doi.org/10.1103/PhysRevLett.101.055504
|
|
1300
1425
|
"""
|
|
1301
|
-
C = self._unrotate()
|
|
1426
|
+
C = self._unrotate() # Ensure that the averages do not use the orientations
|
|
1302
1427
|
Cvoigt = C.Voigt_average()
|
|
1303
1428
|
Creuss = C.Reuss_average()
|
|
1304
1429
|
Gv = Cvoigt.matrix[3, 3]
|
|
@@ -1353,6 +1478,123 @@ class StiffnessTensor(SymmetricTensor):
|
|
|
1353
1478
|
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
1354
1479
|
return matgenElast.ElasticTensor(self.full_tensor())
|
|
1355
1480
|
|
|
1481
|
+
def to_Kelvin(self):
|
|
1482
|
+
"""
|
|
1483
|
+
Returns all the tensor components using the Kelvin(-Mandel) mapping convention.
|
|
1484
|
+
|
|
1485
|
+
Returns
|
|
1486
|
+
-------
|
|
1487
|
+
numpy.ndarray
|
|
1488
|
+
(6,6) matrix, according to the Kelvin mapping
|
|
1489
|
+
|
|
1490
|
+
See Also
|
|
1491
|
+
--------
|
|
1492
|
+
eig : returns the eigenvalues and the eigenvectors of the Kelvin's matrix
|
|
1493
|
+
from_Kelvin : Construct a fourth-order tensor from its (6,6) Kelvin matrix
|
|
1494
|
+
|
|
1495
|
+
Notes
|
|
1496
|
+
-----
|
|
1497
|
+
This mapping convention is discussed in [4]_.
|
|
1498
|
+
|
|
1499
|
+
References
|
|
1500
|
+
----------
|
|
1501
|
+
.. [4] Helbig, K. (2013). What Kelvin might have written about Elasticity. Geophysical Prospecting, 61(1), 1-20.
|
|
1502
|
+
doi: 10.1111/j.1365-2478.2011.01049.x
|
|
1503
|
+
"""
|
|
1504
|
+
return self.matrix /self.voigt_map * _voigt_to_kelvin_matrix
|
|
1505
|
+
|
|
1506
|
+
def eig(self):
|
|
1507
|
+
"""
|
|
1508
|
+
Compute the eigenstiffnesses and the eigenstrains.
|
|
1509
|
+
|
|
1510
|
+
Solve the eigenvalue problem from the Kelvin matrix of the stiffness tensor (see Notes).
|
|
1511
|
+
|
|
1512
|
+
Returns
|
|
1513
|
+
-------
|
|
1514
|
+
numpy.ndarray
|
|
1515
|
+
Array of 6 eigenstiffnesses (eigenvalues of the stiffness matrix)
|
|
1516
|
+
numpy.ndarray
|
|
1517
|
+
(6,6) array of eigenstrains (eigenvectors of the stiffness matrix)
|
|
1518
|
+
|
|
1519
|
+
See Also
|
|
1520
|
+
--------
|
|
1521
|
+
to_Kelvin : returns the stiffness components as a (6,6) matrix, according to the Kelvin mapping convention.
|
|
1522
|
+
eig_stiffnesses : returns the eigenstiffnesses only
|
|
1523
|
+
eig_strains : returns the eigenstrains only
|
|
1524
|
+
|
|
1525
|
+
Notes
|
|
1526
|
+
-----
|
|
1527
|
+
The definition for eigenstiffnesses and the eigenstrains are introduced in [4]_.
|
|
1528
|
+
"""
|
|
1529
|
+
return np.linalg.eigh(self.to_Kelvin())
|
|
1530
|
+
|
|
1531
|
+
@property
|
|
1532
|
+
def eig_stiffnesses(self):
|
|
1533
|
+
"""
|
|
1534
|
+
Compute the eigenstiffnesses given by the Kelvin's matrix for stiffness.
|
|
1535
|
+
|
|
1536
|
+
Returns
|
|
1537
|
+
-------
|
|
1538
|
+
numpy.ndarray
|
|
1539
|
+
6 eigenvalues of the Kelvin's stiffness matrix, in ascending order
|
|
1540
|
+
|
|
1541
|
+
See Also
|
|
1542
|
+
--------
|
|
1543
|
+
eig : returns the eigenstiffnesses and the eigenstrains
|
|
1544
|
+
eig_strains : returns the eigenstrains only
|
|
1545
|
+
"""
|
|
1546
|
+
return np.linalg.eigvalsh(self.to_Kelvin())
|
|
1547
|
+
|
|
1548
|
+
@property
|
|
1549
|
+
def eig_strains(self):
|
|
1550
|
+
"""
|
|
1551
|
+
Compute the eigenstrains from the Kelvin's matrix for stiffness
|
|
1552
|
+
|
|
1553
|
+
Returns
|
|
1554
|
+
-------
|
|
1555
|
+
numpy.ndarray
|
|
1556
|
+
(6,6) matrix of eigenstrains, sorted by ascending order of eigenstiffnesses.
|
|
1557
|
+
|
|
1558
|
+
See Also
|
|
1559
|
+
--------
|
|
1560
|
+
eig : returns both the eigenvalues and the eigenvectors of the Kelvin matrix
|
|
1561
|
+
"""
|
|
1562
|
+
return self.eig()[1]
|
|
1563
|
+
|
|
1564
|
+
@property
|
|
1565
|
+
def eig_compliances(self):
|
|
1566
|
+
"""
|
|
1567
|
+
Compute the eigencompliances from the Kelvin's matrix of stiffness
|
|
1568
|
+
|
|
1569
|
+
Returns
|
|
1570
|
+
-------
|
|
1571
|
+
numpy.ndarray
|
|
1572
|
+
Inverses of the 6 eigenvalues of the Kelvin's stiffness matrix, in descending order
|
|
1573
|
+
|
|
1574
|
+
See Also
|
|
1575
|
+
--------
|
|
1576
|
+
eig_stiffnesses : compute the eigenstiffnesses from the Kelvin's matrix of stiffness
|
|
1577
|
+
"""
|
|
1578
|
+
return 1/self.eig_stiffnesses
|
|
1579
|
+
|
|
1580
|
+
@classmethod
|
|
1581
|
+
def from_Kelvin(cls, matrix, **kwargs):
|
|
1582
|
+
"""
|
|
1583
|
+
Create a tensor from the (6,6) matrix following the Kelvin(-Mandel) mapping convention
|
|
1584
|
+
|
|
1585
|
+
Parameters
|
|
1586
|
+
----------
|
|
1587
|
+
matrix : list or numpy.ndarray
|
|
1588
|
+
(6,6) matrix of components
|
|
1589
|
+
kwargs : dict
|
|
1590
|
+
keyword arguments passed to the constructor
|
|
1591
|
+
Returns
|
|
1592
|
+
-------
|
|
1593
|
+
StiffnessTensor
|
|
1594
|
+
"""
|
|
1595
|
+
return cls(matrix * cls.voigt_map / _voigt_to_kelvin_matrix, **kwargs)
|
|
1596
|
+
|
|
1597
|
+
|
|
1356
1598
|
class ComplianceTensor(StiffnessTensor):
|
|
1357
1599
|
"""
|
|
1358
1600
|
Class for manipulating compliance tensors
|
|
@@ -1405,7 +1647,7 @@ class ComplianceTensor(StiffnessTensor):
|
|
|
1405
1647
|
mat = _isotropic_matrix(S11, S12, S44)
|
|
1406
1648
|
return ComplianceTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
1407
1649
|
else:
|
|
1408
|
-
return self.
|
|
1650
|
+
return ComplianceTensor(self.mean())
|
|
1409
1651
|
|
|
1410
1652
|
def Voigt_average(self):
|
|
1411
1653
|
return self.inv().Voigt_average().inv()
|
|
@@ -1424,14 +1666,14 @@ class ComplianceTensor(StiffnessTensor):
|
|
|
1424
1666
|
@classmethod
|
|
1425
1667
|
def transverse_isotropic(cls, *args, **kwargs):
|
|
1426
1668
|
return super().transverse_isotropic(*args, **kwargs).inv()
|
|
1427
|
-
|
|
1669
|
+
|
|
1428
1670
|
@classmethod
|
|
1429
1671
|
def weighted_average(cls, *args):
|
|
1430
1672
|
return super().weighted_average(*args).inv()
|
|
1431
1673
|
|
|
1432
1674
|
@property
|
|
1433
1675
|
def bulk_modulus(self):
|
|
1434
|
-
return 1/np.sum(self.matrix[0:3,0:3])
|
|
1676
|
+
return 1 / np.sum(self.matrix[0:3, 0:3])
|
|
1435
1677
|
|
|
1436
1678
|
@property
|
|
1437
1679
|
def universal_anisotropy(self):
|
|
@@ -1461,3 +1703,77 @@ class ComplianceTensor(StiffnessTensor):
|
|
|
1461
1703
|
except ImportError:
|
|
1462
1704
|
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
1463
1705
|
return matgenElast.ComplianceTensor(self.full_tensor())
|
|
1706
|
+
|
|
1707
|
+
def eig(self):
|
|
1708
|
+
"""
|
|
1709
|
+
Compute the eigencompliances and the eigenstresses.
|
|
1710
|
+
|
|
1711
|
+
Solve the eigenvalue problem from the Kelvin matrix of the compliance tensor (see Notes).
|
|
1712
|
+
|
|
1713
|
+
Returns
|
|
1714
|
+
-------
|
|
1715
|
+
numpy.ndarray
|
|
1716
|
+
Array of 6 eigencompliances (eigenvalues of the stiffness matrix)
|
|
1717
|
+
numpy.ndarray
|
|
1718
|
+
(6,6) array of eigenstresses (eigenvectors of the stiffness matrix)
|
|
1719
|
+
|
|
1720
|
+
See Also
|
|
1721
|
+
--------
|
|
1722
|
+
Kelvin : returns the stiffness components as a (6,6) matrix, according to the Kelvin mapping convention.
|
|
1723
|
+
eig_compliances : returns the eigencompliances only
|
|
1724
|
+
eig_stresses : returns the eigenstresses only
|
|
1725
|
+
|
|
1726
|
+
Notes
|
|
1727
|
+
-----
|
|
1728
|
+
The definition for eigencompliances and the eigenstresses are introduced in [4]_.
|
|
1729
|
+
"""
|
|
1730
|
+
return np.linalg.eigh(self.to_Kelvin())
|
|
1731
|
+
|
|
1732
|
+
@property
|
|
1733
|
+
def eig_compliances(self):
|
|
1734
|
+
"""
|
|
1735
|
+
Compute the eigencompliances given by the Kelvin's matrix for stiffness.
|
|
1736
|
+
|
|
1737
|
+
Returns
|
|
1738
|
+
-------
|
|
1739
|
+
numpy.ndarray
|
|
1740
|
+
6 eigenvalues of the Kelvin's compliance matrix, in ascending order
|
|
1741
|
+
|
|
1742
|
+
See Also
|
|
1743
|
+
--------
|
|
1744
|
+
eig : returns the eigencompliances and the eigenstresses
|
|
1745
|
+
eig_strains : returns the eigenstresses only
|
|
1746
|
+
"""
|
|
1747
|
+
return np.linalg.eigvalsh(self.to_Kelvin())
|
|
1748
|
+
|
|
1749
|
+
@property
|
|
1750
|
+
def eig_stresses(self):
|
|
1751
|
+
"""
|
|
1752
|
+
Compute the eigenstresses from the Kelvin's matrix for stiffness
|
|
1753
|
+
|
|
1754
|
+
Returns
|
|
1755
|
+
-------
|
|
1756
|
+
numpy.ndarray
|
|
1757
|
+
(6,6) matrix of eigenstresses, sorted by ascending order of eigencompliances.
|
|
1758
|
+
|
|
1759
|
+
See Also
|
|
1760
|
+
--------
|
|
1761
|
+
eig : returns both the eigencompliances and the eigenstresses
|
|
1762
|
+
"""
|
|
1763
|
+
return self.eig()[1]
|
|
1764
|
+
|
|
1765
|
+
@property
|
|
1766
|
+
def eig_stiffnesses(self):
|
|
1767
|
+
"""
|
|
1768
|
+
Compute the eigenstiffnesses from the Kelvin's matrix of compliance
|
|
1769
|
+
|
|
1770
|
+
Returns
|
|
1771
|
+
-------
|
|
1772
|
+
numpy.ndarray
|
|
1773
|
+
inverses of 6 eigenvalues of the Kelvin's compliance matrix, in descending order
|
|
1774
|
+
|
|
1775
|
+
See Also
|
|
1776
|
+
--------
|
|
1777
|
+
eig_compliances : compute the eigencompliances from the Kelvin's matrix of compliance
|
|
1778
|
+
"""
|
|
1779
|
+
return 1/self.eig_compliances
|