elasticipy 3.0.0__py3-none-any.whl → 4.1.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 +16 -1779
- Elasticipy/StressStrainTensors.py +16 -138
- Elasticipy/ThermalExpansion.py +9 -246
- Elasticipy/gui.py +3 -3
- Elasticipy/interfaces/FEPX.py +119 -0
- Elasticipy/interfaces/PRISMS.py +103 -0
- Elasticipy/{Plasticity.py → plasticity.py} +35 -2
- Elasticipy/{PoleFigure.py → polefigure.py} +21 -0
- Elasticipy/{SphericalFunction.py → spherical_function.py} +3 -15
- Elasticipy/tensors/__init__.py +0 -0
- Elasticipy/tensors/elasticity.py +1748 -0
- Elasticipy/tensors/fourth_order.py +662 -0
- Elasticipy/tensors/mapping.py +44 -0
- Elasticipy/{SecondOrderTensor.py → tensors/second_order.py} +113 -9
- Elasticipy/tensors/stress_strain.py +154 -0
- Elasticipy/tensors/thermal_expansion.py +249 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.1.0.dist-info}/METADATA +26 -8
- elasticipy-4.1.0.dist-info/RECORD +23 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.1.0.dist-info}/WHEEL +1 -1
- elasticipy-3.0.0.dist-info/RECORD +0 -15
- /Elasticipy/{CrystalSymmetries.py → crystal_symmetries.py} +0 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.1.0.dist-info/licenses}/LICENSE +0 -0
- {elasticipy-3.0.0.dist-info → elasticipy-4.1.0.dist-info}/top_level.txt +0 -0
Elasticipy/FourthOrderTensor.py
CHANGED
|
@@ -1,1779 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[a, a, a, 2, 2, 2],
|
|
18
|
-
[a, a, a, 2, 2, 2],
|
|
19
|
-
[a, a, a, 2, 2, 2],])
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _parse_tensor_components(prefix, **kwargs):
|
|
23
|
-
pattern = r'^{}(\d{{2}})$'.format(prefix)
|
|
24
|
-
value = dict()
|
|
25
|
-
for k, v in kwargs.items():
|
|
26
|
-
match = re.match(pattern, k) # Extract 'C11' to '11' and so
|
|
27
|
-
if match:
|
|
28
|
-
value[match.group(1)] = v
|
|
29
|
-
return value
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _indices2str(ij):
|
|
33
|
-
return f'{ij[0] + 1}{ij[1] + 1}'
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def voigt_indices(i, j):
|
|
37
|
-
"""
|
|
38
|
-
Translate the two-index notation to one-index notation
|
|
39
|
-
|
|
40
|
-
Parameters
|
|
41
|
-
----------
|
|
42
|
-
i : int or np.ndarray
|
|
43
|
-
First index
|
|
44
|
-
j : int or np.ndarray
|
|
45
|
-
Second index
|
|
46
|
-
|
|
47
|
-
Returns
|
|
48
|
-
-------
|
|
49
|
-
Index in the vector of length 6
|
|
50
|
-
"""
|
|
51
|
-
voigt_mat = np.array([[0, 5, 4],
|
|
52
|
-
[5, 1, 3],
|
|
53
|
-
[4, 3, 2]])
|
|
54
|
-
return voigt_mat[i, j]
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def unvoigt_index(i):
|
|
58
|
-
"""
|
|
59
|
-
Translate the one-index notation to two-index notation
|
|
60
|
-
|
|
61
|
-
Parameters
|
|
62
|
-
----------
|
|
63
|
-
i : int or np.ndarray
|
|
64
|
-
Index to translate
|
|
65
|
-
"""
|
|
66
|
-
inverse_voigt_mat = np.array([[0, 0],
|
|
67
|
-
[1, 1],
|
|
68
|
-
[2, 2],
|
|
69
|
-
[1, 2],
|
|
70
|
-
[0, 2],
|
|
71
|
-
[0, 1]])
|
|
72
|
-
return inverse_voigt_mat[i]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _compute_unit_strain_along_direction(S, m, n, direction='longitudinal'):
|
|
76
|
-
if not isinstance(S, ComplianceTensor):
|
|
77
|
-
S = S.inv()
|
|
78
|
-
if direction == 'transverse':
|
|
79
|
-
ein_str = 'ijkl,pi,pj,pk,pl->p'
|
|
80
|
-
return np.einsum(ein_str, S.full_tensor(), m, m, n, n)
|
|
81
|
-
elif direction == 'longitudinal':
|
|
82
|
-
ein_str = 'ijkl,pi,pk,pj,pl->p'
|
|
83
|
-
return np.einsum(ein_str, S.full_tensor(), m, m, n, n)
|
|
84
|
-
else:
|
|
85
|
-
ein_str = 'ijkk,pi,pj->p'
|
|
86
|
-
return np.einsum(ein_str, S.full_tensor(), m, m)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _isotropic_matrix(C11, C12, C44):
|
|
90
|
-
return np.array([[C11, C12, C12, 0, 0, 0],
|
|
91
|
-
[C12, C11, C12, 0, 0, 0],
|
|
92
|
-
[C12, C12, C11, 0, 0, 0],
|
|
93
|
-
[0, 0, 0, C44, 0, 0],
|
|
94
|
-
[0, 0, 0, 0, C44, 0],
|
|
95
|
-
[0, 0, 0, 0, 0, C44]])
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _check_definite_positive(mat):
|
|
99
|
-
try:
|
|
100
|
-
np.linalg.cholesky(mat)
|
|
101
|
-
except np.linalg.LinAlgError:
|
|
102
|
-
eigen_val = np.linalg.eigvals(mat)
|
|
103
|
-
raise ValueError('The input matrix is not definite positive (eigenvalues: {})'.format(eigen_val))
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def _rotate_tensor(full_tensor, rotation):
|
|
107
|
-
rot_mat = rotation_to_matrix(rotation)
|
|
108
|
-
str_ein = '...im,...jn,...ko,...lp,mnop->...ijkl'
|
|
109
|
-
return np.einsum(str_ein, rot_mat, rot_mat, rot_mat, rot_mat, full_tensor)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class SymmetricTensor:
|
|
113
|
-
"""
|
|
114
|
-
Template class for manipulating symmetric fourth-order tensors.
|
|
115
|
-
|
|
116
|
-
Attributes
|
|
117
|
-
----------
|
|
118
|
-
matrix : np.ndarray
|
|
119
|
-
(6,6) matrix gathering all the components of the tensor, using the Voigt notation.
|
|
120
|
-
symmetry : str
|
|
121
|
-
Symmetry of the tensor
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
tensor_name = 'Symmetric'
|
|
125
|
-
voigt_map = np.ones((6, 6))
|
|
126
|
-
C11_C12_factor = 0.5
|
|
127
|
-
C46_C56_factor = 1.0
|
|
128
|
-
component_prefix = 'C'
|
|
129
|
-
|
|
130
|
-
def __init__(self, M, phase_name=None, symmetry='Triclinic', orientations=None,
|
|
131
|
-
check_symmetry=True, check_positive_definite=False):
|
|
132
|
-
"""
|
|
133
|
-
Construct of stiffness tensor from a (6,6) matrix.
|
|
134
|
-
|
|
135
|
-
The input matrix must be symmetric, otherwise an error is thrown (except if check_symmetry==False, see below)
|
|
136
|
-
|
|
137
|
-
Parameters
|
|
138
|
-
----------
|
|
139
|
-
M : np.ndarray
|
|
140
|
-
(6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
141
|
-
(3,3,3,3).
|
|
142
|
-
phase_name : str, default None
|
|
143
|
-
Name to display
|
|
144
|
-
symmetry : str, default Triclinic
|
|
145
|
-
Name of the crystal's symmetry
|
|
146
|
-
check_symmetry : bool, optional
|
|
147
|
-
Whether to check or not that the input matrix is symmetric.
|
|
148
|
-
check_positive_definite : bool, optional
|
|
149
|
-
Whether to check or not that the input matrix is definite positive
|
|
150
|
-
"""
|
|
151
|
-
M = np.asarray(M)
|
|
152
|
-
if M.shape == (6, 6):
|
|
153
|
-
matrix = M
|
|
154
|
-
elif M.shape == (3, 3, 3, 3):
|
|
155
|
-
matrix = self._full_to_matrix(M)
|
|
156
|
-
else:
|
|
157
|
-
raise ValueError('The input matrix must of shape (6,6)')
|
|
158
|
-
if check_symmetry and not np.all(np.isclose(matrix, matrix.T)):
|
|
159
|
-
raise ValueError('The input matrix must be symmetric')
|
|
160
|
-
if check_positive_definite:
|
|
161
|
-
_check_definite_positive(matrix)
|
|
162
|
-
|
|
163
|
-
self.matrix = matrix
|
|
164
|
-
self.phase_name = phase_name
|
|
165
|
-
self.symmetry = symmetry
|
|
166
|
-
self.orientations = orientations
|
|
167
|
-
|
|
168
|
-
for i in range(0, 6):
|
|
169
|
-
for j in range(0, 6):
|
|
170
|
-
def getter(obj, I=i, J=j):
|
|
171
|
-
return obj.matrix[I, J]
|
|
172
|
-
|
|
173
|
-
getter.__doc__ = f"Returns the ({i + 1},{j + 1}) component of the {self.tensor_name} matrix."
|
|
174
|
-
component_name = 'C{}{}'.format(i + 1, j + 1)
|
|
175
|
-
setattr(self.__class__, component_name, property(getter)) # Dynamically create the property
|
|
176
|
-
|
|
177
|
-
def __repr__(self):
|
|
178
|
-
if self.phase_name is None:
|
|
179
|
-
heading = '{} tensor (in Voigt notation):\n'.format(self.tensor_name)
|
|
180
|
-
else:
|
|
181
|
-
heading = '{} tensor (in Voigt notation) for {}:\n'.format(self.tensor_name, self.phase_name)
|
|
182
|
-
print_symmetry = '\nSymmetry: {}'.format(self.symmetry)
|
|
183
|
-
msg = heading + self.matrix.__str__() + print_symmetry
|
|
184
|
-
if self.orientations is not None:
|
|
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)
|
|
190
|
-
return msg
|
|
191
|
-
|
|
192
|
-
def __len__(self):
|
|
193
|
-
o = self.orientations
|
|
194
|
-
if o is None:
|
|
195
|
-
return 1
|
|
196
|
-
else:
|
|
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)
|
|
216
|
-
|
|
217
|
-
def full_tensor(self):
|
|
218
|
-
"""
|
|
219
|
-
Returns the full (unvoigted) tensor, as a [3, 3, 3, 3] array
|
|
220
|
-
|
|
221
|
-
Returns
|
|
222
|
-
-------
|
|
223
|
-
np.ndarray
|
|
224
|
-
Full tensor (4-index notation)
|
|
225
|
-
"""
|
|
226
|
-
i, j, k, ell = np.indices((3, 3, 3, 3))
|
|
227
|
-
ij = voigt_indices(i, j)
|
|
228
|
-
kl = voigt_indices(k, ell)
|
|
229
|
-
m = self.matrix[ij, kl] / self.voigt_map[ij, kl]
|
|
230
|
-
if self.orientations is None:
|
|
231
|
-
return m
|
|
232
|
-
else:
|
|
233
|
-
return _rotate_tensor(m, self.orientations)
|
|
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
|
-
|
|
255
|
-
@classmethod
|
|
256
|
-
def _full_to_matrix(cls, full_tensor):
|
|
257
|
-
ij, kl = np.indices((6, 6))
|
|
258
|
-
i, j = unvoigt_index(ij).T
|
|
259
|
-
k, ell = unvoigt_index(kl).T
|
|
260
|
-
return full_tensor[i, j, k, ell] * cls.voigt_map[ij, kl]
|
|
261
|
-
|
|
262
|
-
def rotate(self, rotation):
|
|
263
|
-
"""
|
|
264
|
-
Apply a single rotation to a tensor, and return its component into the rotated frame.
|
|
265
|
-
|
|
266
|
-
Parameters
|
|
267
|
-
----------
|
|
268
|
-
rotation : Rotation or orix.quaternion.rotation.Rotation
|
|
269
|
-
Rotation to apply
|
|
270
|
-
|
|
271
|
-
Returns
|
|
272
|
-
-------
|
|
273
|
-
SymmetricTensor
|
|
274
|
-
Rotated tensor
|
|
275
|
-
"""
|
|
276
|
-
if _is_single_rotation(rotation):
|
|
277
|
-
rotated_tensor = _rotate_tensor(self.full_tensor(), rotation)
|
|
278
|
-
rotated_matrix = self._full_to_matrix(rotated_tensor)
|
|
279
|
-
return self.__class__(rotated_matrix)
|
|
280
|
-
else:
|
|
281
|
-
raise ValueError('The rotation to apply must be single')
|
|
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
|
-
|
|
318
|
-
def _unrotate(self):
|
|
319
|
-
unrotated_tensor = deepcopy(self)
|
|
320
|
-
unrotated_tensor.orientations = None
|
|
321
|
-
return unrotated_tensor
|
|
322
|
-
|
|
323
|
-
def __add__(self, other):
|
|
324
|
-
if isinstance(other, np.ndarray):
|
|
325
|
-
if other.shape == (6, 6):
|
|
326
|
-
mat = self.matrix + other
|
|
327
|
-
elif other.shape == (3, 3, 3, 3):
|
|
328
|
-
mat = self._full_to_matrix(self.full_tensor() + other)
|
|
329
|
-
else:
|
|
330
|
-
raise ValueError('The input argument must be either a 6x6 matrix or a (3,3,3,3) array.')
|
|
331
|
-
elif isinstance(other, SymmetricTensor):
|
|
332
|
-
if type(other) == type(self):
|
|
333
|
-
mat = self.matrix + other.matrix
|
|
334
|
-
else:
|
|
335
|
-
raise ValueError('The two tensors to add must be of the same class.')
|
|
336
|
-
else:
|
|
337
|
-
raise ValueError('I don''t know how to add {} with {}.'.format(type(self), type(other)))
|
|
338
|
-
return self.__class__(mat)
|
|
339
|
-
|
|
340
|
-
def __sub__(self, other):
|
|
341
|
-
if isinstance(other, SymmetricTensor):
|
|
342
|
-
return self.__add__(-other.matrix)
|
|
343
|
-
else:
|
|
344
|
-
return self.__add__(-other)
|
|
345
|
-
|
|
346
|
-
def __mul__(self, other):
|
|
347
|
-
if isinstance(other, SymmetricSecondOrderTensor):
|
|
348
|
-
return SymmetricSecondOrderTensor(self * other.matrix)
|
|
349
|
-
elif isinstance(other, np.ndarray):
|
|
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]))
|
|
371
|
-
elif isinstance(other, Rotation) or is_orix_rotation(other):
|
|
372
|
-
if _is_single_rotation(other):
|
|
373
|
-
return self.rotate(other)
|
|
374
|
-
else:
|
|
375
|
-
return self.__class__(self.matrix, symmetry=self.symmetry, orientations=other,
|
|
376
|
-
phase_name=self.phase_name)
|
|
377
|
-
else:
|
|
378
|
-
return self.__class__(self.matrix * other, symmetry=self.symmetry)
|
|
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
|
-
|
|
398
|
-
def __rmul__(self, other):
|
|
399
|
-
if isinstance(other, (Rotation, float, int, np.number)) or is_orix_rotation(other):
|
|
400
|
-
return self * other
|
|
401
|
-
else:
|
|
402
|
-
raise NotImplementedError('A fourth order tensor can be left-multiplied by rotations or scalar only.')
|
|
403
|
-
|
|
404
|
-
def __truediv__(self, other):
|
|
405
|
-
if isinstance(other, (float, int, np.number)):
|
|
406
|
-
return self.__class__(self.matrix / other, symmetry=self.symmetry)
|
|
407
|
-
else:
|
|
408
|
-
raise NotImplementedError
|
|
409
|
-
|
|
410
|
-
def __eq__(self, other):
|
|
411
|
-
if isinstance(other, SymmetricTensor):
|
|
412
|
-
return np.all(self.matrix == other.matrix) and np.all(self.orientations == other.orientations)
|
|
413
|
-
elif isinstance(other, np.ndarray) and other.shape == (6, 6):
|
|
414
|
-
return np.all(self.matrix == other)
|
|
415
|
-
else:
|
|
416
|
-
raise NotImplementedError('The element to compare with must be a fourth-order tensor '
|
|
417
|
-
'or an array of shape (6,6).')
|
|
418
|
-
|
|
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)
|
|
431
|
-
|
|
432
|
-
@classmethod
|
|
433
|
-
def _matrixFromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', prefix=None, **kwargs):
|
|
434
|
-
if prefix is None:
|
|
435
|
-
prefix = cls.component_prefix
|
|
436
|
-
values = _parse_tensor_components(prefix, **kwargs)
|
|
437
|
-
C = np.zeros((6, 6))
|
|
438
|
-
symmetry = symmetry.capitalize()
|
|
439
|
-
if ((symmetry == 'tetragonal') or (symmetry == 'trigonal')) and (point_group is None):
|
|
440
|
-
raise ValueError('For tetragonal and trigonal symmetries, the point group is mandatory.')
|
|
441
|
-
tetra_1 = ['4', '-4', '4/m']
|
|
442
|
-
tetra_2 = ['4mm', '-42m', '422', '4/mmm']
|
|
443
|
-
trigo_1 = ['3', '-3']
|
|
444
|
-
trigo_2 = ['32', '-3m', '3m']
|
|
445
|
-
if point_group is not None:
|
|
446
|
-
if (point_group in tetra_1) or (point_group in tetra_2):
|
|
447
|
-
symmetry = 'Tetragonal'
|
|
448
|
-
elif (point_group in trigo_1) or (point_group in trigo_2):
|
|
449
|
-
symmetry = 'Trigonal'
|
|
450
|
-
symmetry_description = SYMMETRIES[symmetry]
|
|
451
|
-
if symmetry == 'Tetragonal':
|
|
452
|
-
if point_group in tetra_1:
|
|
453
|
-
symmetry_description = symmetry_description[', '.join(tetra_1)]
|
|
454
|
-
else:
|
|
455
|
-
symmetry_description = symmetry_description[', '.join(tetra_2)]
|
|
456
|
-
elif symmetry == 'Trigonal':
|
|
457
|
-
if point_group in trigo_1:
|
|
458
|
-
symmetry_description = symmetry_description[', '.join(trigo_1)]
|
|
459
|
-
else:
|
|
460
|
-
symmetry_description = symmetry_description[', '.join(trigo_2)]
|
|
461
|
-
elif symmetry == 'Monoclinic':
|
|
462
|
-
symmetry_description = symmetry_description["Diad || " + diad]
|
|
463
|
-
for required_field in symmetry_description.required:
|
|
464
|
-
C[required_field] = values[_indices2str(required_field)]
|
|
465
|
-
|
|
466
|
-
# Now apply relationships between components
|
|
467
|
-
for equality in symmetry_description.equal:
|
|
468
|
-
for index in equality[1]:
|
|
469
|
-
C[index] = C[equality[0]]
|
|
470
|
-
for opposite in symmetry_description.opposite:
|
|
471
|
-
for index in opposite[1]:
|
|
472
|
-
C[index] = -C[opposite[0]]
|
|
473
|
-
C11_C12 = symmetry_description.C11_C12
|
|
474
|
-
if C11_C12:
|
|
475
|
-
for index in C11_C12:
|
|
476
|
-
C[index] = (C[0, 0] - C[0, 1]) * cls.C11_C12_factor
|
|
477
|
-
|
|
478
|
-
if symmetry == 'Trigonal':
|
|
479
|
-
C[3, 5] = cls.C46_C56_factor * C[3, 5]
|
|
480
|
-
C[4, 5] = cls.C46_C56_factor * C[4, 5]
|
|
481
|
-
|
|
482
|
-
return C + np.tril(C.T, -1)
|
|
483
|
-
|
|
484
|
-
@classmethod
|
|
485
|
-
def fromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', phase_name=None, prefix=None,
|
|
486
|
-
**kwargs):
|
|
487
|
-
"""
|
|
488
|
-
Create a fourth-order tensor from limited number of components, taking advantage of crystallographic symmetries
|
|
489
|
-
|
|
490
|
-
Parameters
|
|
491
|
-
----------
|
|
492
|
-
symmetry : str, default Triclinic
|
|
493
|
-
Name of the crystallographic symmetry
|
|
494
|
-
point_group : str
|
|
495
|
-
Point group of the considered crystal. Only used (and mandatory) for tetragonal and trigonal symmetries.
|
|
496
|
-
diad : str {'x', 'y'}, default 'x'
|
|
497
|
-
Alignment convention. Sets whether x||a or y||b. Only used for monoclinic symmetry.
|
|
498
|
-
phase_name : str, default None
|
|
499
|
-
Name to use when printing the tensor
|
|
500
|
-
prefix : str, default None
|
|
501
|
-
Define the prefix to use when providing the components. By default, it is 'C' for stiffness tensors, 'S' for
|
|
502
|
-
compliance.
|
|
503
|
-
kwargs
|
|
504
|
-
Keywords describing all the necessary components, depending on the crystal's symmetry and the type of tensor.
|
|
505
|
-
For Stiffness, they should be named as 'Cij' (e.g. C11=..., C12=...).
|
|
506
|
-
For Comliance, they should be named as 'Sij' (e.g. S11=..., S12=...).
|
|
507
|
-
See examples below. The behaviour can be overriten with the prefix option (see above)
|
|
508
|
-
|
|
509
|
-
Returns
|
|
510
|
-
-------
|
|
511
|
-
FourthOrderTensor
|
|
512
|
-
|
|
513
|
-
See Also
|
|
514
|
-
--------
|
|
515
|
-
StiffnessTensor.isotropic : creates an isotropic stiffness tensor from two paremeters (e.g. E and v).
|
|
516
|
-
|
|
517
|
-
Notes
|
|
518
|
-
-----
|
|
519
|
-
The relationships between the tensor's components depend on the crystallogrpahic symmetry [1]_.
|
|
520
|
-
|
|
521
|
-
References
|
|
522
|
-
----------
|
|
523
|
-
.. [1] Nye, J. F. Physical Properties of Crystals. London: Oxford University Press, 1959.
|
|
524
|
-
|
|
525
|
-
Examples
|
|
526
|
-
--------
|
|
527
|
-
>>> from Elasticipy.FourthOrderTensor import StiffnessTensor\n
|
|
528
|
-
>>> StiffnessTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
|
|
529
|
-
... C11=231, C12=127, C13=104,
|
|
530
|
-
... C22=240, C23=131, C33=175,
|
|
531
|
-
... C44=81, C55=11, C66=85,
|
|
532
|
-
... C15=-18, C25=1, C35=-3, C46=3)
|
|
533
|
-
Stiffness tensor (in Voigt notation) for TiNi:
|
|
534
|
-
[[231. 127. 104. 0. -18. 0.]
|
|
535
|
-
[127. 240. 131. 0. 1. 0.]
|
|
536
|
-
[104. 131. 175. 0. -3. 0.]
|
|
537
|
-
[ 0. 0. 0. 81. 0. 3.]
|
|
538
|
-
[-18. 1. -3. 0. 11. 0.]
|
|
539
|
-
[ 0. 0. 0. 3. 0. 85.]]
|
|
540
|
-
Symmetry: monoclinic
|
|
541
|
-
|
|
542
|
-
>>> from Elasticipy.FourthOrderTensor import ComplianceTensor\n
|
|
543
|
-
>>> ComplianceTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
|
|
544
|
-
... S11=8, S12=-3, S13=-2,
|
|
545
|
-
... S22=8, S23=-5, S33=10,
|
|
546
|
-
... S44=12, S55=116, S66=12,
|
|
547
|
-
... S15=14, S25=-8, S35=0, S46=0)
|
|
548
|
-
Compliance tensor (in Voigt notation) for TiNi:
|
|
549
|
-
[[ 8. -3. -2. 0. 14. 0.]
|
|
550
|
-
[ -3. 8. -5. 0. -8. 0.]
|
|
551
|
-
[ -2. -5. 10. 0. 0. 0.]
|
|
552
|
-
[ 0. 0. 0. 12. 0. 0.]
|
|
553
|
-
[ 14. -8. 0. 0. 116. 0.]
|
|
554
|
-
[ 0. 0. 0. 0. 0. 12.]]
|
|
555
|
-
Symmetry: monoclinic
|
|
556
|
-
"""
|
|
557
|
-
matrix = cls._matrixFromCrystalSymmetry(point_group=point_group, diad=diad, symmetry=symmetry, prefix=prefix,
|
|
558
|
-
**kwargs)
|
|
559
|
-
return cls(matrix, symmetry=symmetry, phase_name=phase_name)
|
|
560
|
-
|
|
561
|
-
@classmethod
|
|
562
|
-
def hexagonal(cls, *, C11=0., C12=0., C13=0., C33=0., C44=0., phase_name=None):
|
|
563
|
-
"""
|
|
564
|
-
Create a fourth-order tensor from hexagonal symmetry.
|
|
565
|
-
|
|
566
|
-
Parameters
|
|
567
|
-
----------
|
|
568
|
-
C11, C12 , C13, C33, C44 : float
|
|
569
|
-
Components of the tensor, using the Voigt notation
|
|
570
|
-
phase_name : str, optional
|
|
571
|
-
Phase name to display
|
|
572
|
-
Returns
|
|
573
|
-
-------
|
|
574
|
-
FourthOrderTensor
|
|
575
|
-
|
|
576
|
-
See Also
|
|
577
|
-
--------
|
|
578
|
-
transverse_isotropic : creates a transverse-isotropic tensor from engineering parameters
|
|
579
|
-
cubic : create a tensor from cubic symmetry
|
|
580
|
-
tetragonal : create a tensor from tetragonal symmetry
|
|
581
|
-
"""
|
|
582
|
-
return cls.fromCrystalSymmetry(symmetry='hexagonal', C11=C11, C12=C12, C13=C13, C33=C33, C44=C44,
|
|
583
|
-
phase_name=phase_name, prefix='C')
|
|
584
|
-
|
|
585
|
-
@classmethod
|
|
586
|
-
def trigonal(cls, *, C11=0., C12=0., C13=0., C14=0., C33=0., C44=0., C15=0., phase_name=None):
|
|
587
|
-
"""
|
|
588
|
-
Create a fourth-order tensor from trigonal symmetry.
|
|
589
|
-
|
|
590
|
-
Parameters
|
|
591
|
-
----------
|
|
592
|
-
C11, C12, C13, C14, C33, C44 : float
|
|
593
|
-
Components of the tensor, using the Voigt notation
|
|
594
|
-
C15 : float, optional
|
|
595
|
-
C15 component of the tensor, only used for point groups 3 and -3.
|
|
596
|
-
phase_name : str, optional
|
|
597
|
-
Phase name to display
|
|
598
|
-
Returns
|
|
599
|
-
-------
|
|
600
|
-
FourthOrderTensor
|
|
601
|
-
|
|
602
|
-
See Also
|
|
603
|
-
--------
|
|
604
|
-
tetragonal : create a tensor from tetragonal symmetry
|
|
605
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
606
|
-
"""
|
|
607
|
-
return cls.fromCrystalSymmetry(point_group='3', C11=C11, C12=C12, C13=C13, C14=C14, C15=C15,
|
|
608
|
-
C33=C33, C44=C44, phase_name=phase_name, prefix='C')
|
|
609
|
-
|
|
610
|
-
@classmethod
|
|
611
|
-
def tetragonal(cls, *, C11=0., C12=0., C13=0., C33=0., C44=0., C16=0., C66=0., phase_name=None):
|
|
612
|
-
"""
|
|
613
|
-
Create a fourth-order tensor from tetragonal symmetry.
|
|
614
|
-
|
|
615
|
-
Parameters
|
|
616
|
-
----------
|
|
617
|
-
C11, C12, C13, C33, C44, C66 : float
|
|
618
|
-
Components of the tensor, using the Voigt notation
|
|
619
|
-
C16 : float, optional
|
|
620
|
-
C16 component in Voigt notation (for point groups 4, -4 and 4/m only)
|
|
621
|
-
phase_name : str, optional
|
|
622
|
-
Phase name to display
|
|
623
|
-
|
|
624
|
-
Returns
|
|
625
|
-
-------
|
|
626
|
-
FourthOrderTensor
|
|
627
|
-
|
|
628
|
-
See Also
|
|
629
|
-
--------
|
|
630
|
-
trigonal : create a tensor from trigonal symmetry
|
|
631
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
632
|
-
"""
|
|
633
|
-
return cls.fromCrystalSymmetry(point_group='4', C11=C11, C12=C12, C13=C13, C16=C16,
|
|
634
|
-
C33=C33, C44=C44, C66=C66, phase_name=phase_name, prefix='C')
|
|
635
|
-
|
|
636
|
-
@classmethod
|
|
637
|
-
def cubic(cls, *, C11=0., C12=0., C44=0., phase_name=None):
|
|
638
|
-
"""
|
|
639
|
-
Create a fourth-order tensor from cubic symmetry.
|
|
640
|
-
|
|
641
|
-
Parameters
|
|
642
|
-
----------
|
|
643
|
-
C11 , C12, C44 : float
|
|
644
|
-
phase_name : str, optional
|
|
645
|
-
Phase name to display
|
|
646
|
-
|
|
647
|
-
Returns
|
|
648
|
-
-------
|
|
649
|
-
StiffnessTensor
|
|
650
|
-
|
|
651
|
-
See Also
|
|
652
|
-
--------
|
|
653
|
-
hexagonal : create a tensor from hexagonal symmetry
|
|
654
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
655
|
-
"""
|
|
656
|
-
return cls.fromCrystalSymmetry(symmetry='cubic', C11=C11, C12=C12, C44=C44, phase_name=phase_name, prefix='C')
|
|
657
|
-
|
|
658
|
-
@classmethod
|
|
659
|
-
def orthorhombic(cls, *, C11=0., C12=0., C13=0., C22=0., C23=0., C33=0., C44=0., C55=0., C66=0., phase_name=None):
|
|
660
|
-
"""
|
|
661
|
-
Create a fourth-order tensor from orthorhombic symmetry.
|
|
662
|
-
|
|
663
|
-
Parameters
|
|
664
|
-
----------
|
|
665
|
-
C11, C12, C13, C22, C23, C33, C44, C55, C66 : float
|
|
666
|
-
Components of the tensor, using the Voigt notation
|
|
667
|
-
phase_name : str, optional
|
|
668
|
-
Phase name to display
|
|
669
|
-
|
|
670
|
-
Returns
|
|
671
|
-
-------
|
|
672
|
-
FourthOrderTensor
|
|
673
|
-
|
|
674
|
-
See Also
|
|
675
|
-
--------
|
|
676
|
-
monoclinic : create a tensor from monoclinic symmetry
|
|
677
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
678
|
-
"""
|
|
679
|
-
return cls.fromCrystalSymmetry(symmetry='orthorhombic',
|
|
680
|
-
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55, C66=C66,
|
|
681
|
-
phase_name=phase_name, prefix='C')
|
|
682
|
-
|
|
683
|
-
@classmethod
|
|
684
|
-
def monoclinic(cls, *, C11=0., C12=0., C13=0., C22=0., C23=0., C33=0., C44=0., C55=0., C66=0.,
|
|
685
|
-
C15=None, C25=None, C35=None, C46=None,
|
|
686
|
-
C16=None, C26=None, C36=None, C45=None,
|
|
687
|
-
phase_name=None):
|
|
688
|
-
"""
|
|
689
|
-
Create a fourth-order tensor from monoclinic symmetry. It automatically detects whether the components are given
|
|
690
|
-
according to the Y or Z diad, depending on the input arguments.
|
|
691
|
-
|
|
692
|
-
For Diad || y, C15, C25, C35 and C46 must be provided.
|
|
693
|
-
For Diad || z, C16, C26, C36 and C45 must be provided.
|
|
694
|
-
|
|
695
|
-
Parameters
|
|
696
|
-
----------
|
|
697
|
-
C11, C12 , C13, C22, C23, C33, C44, C55, C66 : float
|
|
698
|
-
Components of the tensor, using the Voigt notation
|
|
699
|
-
C15 : float, optional
|
|
700
|
-
C15 component of the tensor (if Diad || y)
|
|
701
|
-
C25 : float, optional
|
|
702
|
-
C25 component of the tensor (if Diad || y)
|
|
703
|
-
C35 : float, optional
|
|
704
|
-
C35 component of the tensor (if Diad || y)
|
|
705
|
-
C46 : float, optional
|
|
706
|
-
C46 component of the tensor (if Diad || y)
|
|
707
|
-
C16 : float, optional
|
|
708
|
-
C16 component of the tensor (if Diad || z)
|
|
709
|
-
C26 : float, optional
|
|
710
|
-
C26 component of the tensor (if Diad || z)
|
|
711
|
-
C36 : float, optional
|
|
712
|
-
C36 component of the tensor (if Diad || z)
|
|
713
|
-
C45 : float, optional
|
|
714
|
-
C45 component of the tensor (if Diad || z)
|
|
715
|
-
phase_name : str, optional
|
|
716
|
-
Name to display
|
|
717
|
-
|
|
718
|
-
Returns
|
|
719
|
-
-------
|
|
720
|
-
FourthOrderTensor
|
|
721
|
-
|
|
722
|
-
See Also
|
|
723
|
-
--------
|
|
724
|
-
triclinic : create a tensor from triclinic symmetry
|
|
725
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
726
|
-
"""
|
|
727
|
-
diad_y = not (None in (C15, C25, C35, C46))
|
|
728
|
-
diad_z = not (None in (C16, C26, C36, C45))
|
|
729
|
-
if diad_y and diad_z:
|
|
730
|
-
raise KeyError('Ambiguous diad. Provide either C15, C25, C35 and C46; or C16, C26, C36 and C45')
|
|
731
|
-
elif diad_y:
|
|
732
|
-
return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='y',
|
|
733
|
-
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
|
|
734
|
-
C66=C66,
|
|
735
|
-
C15=C15, C25=C25, C35=C35, C46=C46, phase_name=phase_name, prefix='C')
|
|
736
|
-
elif diad_z:
|
|
737
|
-
return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='z',
|
|
738
|
-
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
|
|
739
|
-
C66=C66,
|
|
740
|
-
C16=C16, C26=C26, C36=C36, C45=C45, phase_name=phase_name, prefix='C')
|
|
741
|
-
else:
|
|
742
|
-
raise KeyError('For monoclinic symmetry, one should provide either C15, C25, C35 and C46, '
|
|
743
|
-
'or C16, C26, C36 and C45.')
|
|
744
|
-
|
|
745
|
-
@classmethod
|
|
746
|
-
def triclinic(cls, C11=0., C12=0., C13=0., C14=0., C15=0., C16=0.,
|
|
747
|
-
C22=0., C23=0., C24=0., C25=0., C26=0.,
|
|
748
|
-
C33=0., C34=0., C35=0., C36=0.,
|
|
749
|
-
C44=0., C45=0., C46=0.,
|
|
750
|
-
C55=0., C56=0.,
|
|
751
|
-
C66=0., phase_name=None):
|
|
752
|
-
"""
|
|
753
|
-
|
|
754
|
-
Parameters
|
|
755
|
-
----------
|
|
756
|
-
C11 , C12 , C13 , C14 , C15 , C16 , C22 , C23 , C24 , C25 , C26 , C33 , C34 , C35 , C36 , C44 , C45 , C46 , C55 , C56 , C66 : float
|
|
757
|
-
Components of the tensor
|
|
758
|
-
phase_name : str, optional
|
|
759
|
-
Name to display
|
|
760
|
-
|
|
761
|
-
Returns
|
|
762
|
-
-------
|
|
763
|
-
FourthOrderTensor
|
|
764
|
-
|
|
765
|
-
See Also
|
|
766
|
-
--------
|
|
767
|
-
monoclinic : create a tensor from monoclinic symmetry
|
|
768
|
-
orthorhombic : create a tensor from orthorhombic symmetry
|
|
769
|
-
"""
|
|
770
|
-
matrix = np.array([[C11, C12, C13, C14, C15, C16],
|
|
771
|
-
[C12, C22, C23, C24, C25, C26],
|
|
772
|
-
[C13, C23, C33, C34, C35, C36],
|
|
773
|
-
[C14, C24, C34, C44, C45, C46],
|
|
774
|
-
[C15, C25, C35, C45, C55, C56],
|
|
775
|
-
[C16, C26, C36, C46, C56, C66]])
|
|
776
|
-
return cls(matrix, phase_name=phase_name)
|
|
777
|
-
|
|
778
|
-
def save_to_txt(self, filename, matrix_only=False):
|
|
779
|
-
"""
|
|
780
|
-
Save the tensor to a text file.
|
|
781
|
-
|
|
782
|
-
Parameters
|
|
783
|
-
----------
|
|
784
|
-
filename : str
|
|
785
|
-
Filename to save the tensor to.
|
|
786
|
-
matrix_only : bool, False
|
|
787
|
-
If true, only the components of tje stiffness tensor is saved (no data about phase nor symmetry)
|
|
788
|
-
|
|
789
|
-
See Also
|
|
790
|
-
--------
|
|
791
|
-
from_txt_file : create a tensor from text file
|
|
792
|
-
|
|
793
|
-
"""
|
|
794
|
-
with open(filename, 'w') as f:
|
|
795
|
-
if not matrix_only:
|
|
796
|
-
if self.phase_name is not None:
|
|
797
|
-
f.write(f"Phase Name: {self.phase_name}\n")
|
|
798
|
-
f.write(f"Symmetry: {self.symmetry}\n")
|
|
799
|
-
for row in self.matrix:
|
|
800
|
-
f.write(" " + " ".join(f"{value:8.2f}" for value in row) + "\n")
|
|
801
|
-
|
|
802
|
-
@classmethod
|
|
803
|
-
def from_txt_file(cls, filename):
|
|
804
|
-
"""
|
|
805
|
-
Load the tensor from a text file.
|
|
806
|
-
|
|
807
|
-
The two first lines can have data about phase name and symmetry, but this is not mandatory.
|
|
808
|
-
|
|
809
|
-
Parameters
|
|
810
|
-
----------
|
|
811
|
-
filename : str
|
|
812
|
-
Filename to load the tensor from.
|
|
813
|
-
|
|
814
|
-
Returns
|
|
815
|
-
-------
|
|
816
|
-
SymmetricTensor
|
|
817
|
-
The reconstructed tensor read from the file.
|
|
818
|
-
|
|
819
|
-
See Also
|
|
820
|
-
--------
|
|
821
|
-
save_to_txt : create a tensor from text file
|
|
822
|
-
|
|
823
|
-
"""
|
|
824
|
-
with open(filename, 'r') as f:
|
|
825
|
-
lines = f.readlines()
|
|
826
|
-
|
|
827
|
-
# Initialize defaults
|
|
828
|
-
phase_name = None
|
|
829
|
-
symmetry = 'Triclinic'
|
|
830
|
-
matrix_start_index = 0
|
|
831
|
-
|
|
832
|
-
# Parse phase name if available
|
|
833
|
-
if lines and lines[0].startswith("Phase Name:"):
|
|
834
|
-
phase_name = lines[0].split(": ", 1)[1].strip()
|
|
835
|
-
matrix_start_index += 1
|
|
836
|
-
|
|
837
|
-
# Parse symmetry if available
|
|
838
|
-
if len(lines) > matrix_start_index and lines[matrix_start_index].startswith("Symmetry:"):
|
|
839
|
-
symmetry = lines[matrix_start_index].split(": ", 1)[1].strip()
|
|
840
|
-
matrix_start_index += 1
|
|
841
|
-
|
|
842
|
-
# Parse matrix
|
|
843
|
-
matrix = np.loadtxt(lines[matrix_start_index:])
|
|
844
|
-
|
|
845
|
-
# Return the reconstructed object
|
|
846
|
-
return cls(matrix, phase_name=phase_name, symmetry=symmetry)
|
|
847
|
-
|
|
848
|
-
def __getitem__(self, item):
|
|
849
|
-
if self.orientations is None:
|
|
850
|
-
raise IndexError('The tensor has no orientation, therefore it cannot be indexed.')
|
|
851
|
-
else:
|
|
852
|
-
return self._unrotate() * self.orientations[item]
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
class StiffnessTensor(SymmetricTensor):
|
|
856
|
-
"""
|
|
857
|
-
Class for manipulating fourth-order stiffness tensors.
|
|
858
|
-
"""
|
|
859
|
-
tensor_name = 'Stiffness'
|
|
860
|
-
C11_C12_factor = 0.5
|
|
861
|
-
|
|
862
|
-
def __init__(self, S, check_positive_definite=True, **kwargs):
|
|
863
|
-
super().__init__(S, check_positive_definite=check_positive_definite, **kwargs)
|
|
864
|
-
|
|
865
|
-
def __mul__(self, other):
|
|
866
|
-
if isinstance(other, StrainTensor):
|
|
867
|
-
new_tensor = super().__mul__(other)
|
|
868
|
-
return StressTensor(new_tensor.matrix)
|
|
869
|
-
elif isinstance(other, StressTensor):
|
|
870
|
-
raise ValueError('You cannot multiply a stiffness tensor with a Stress tensor.')
|
|
871
|
-
else:
|
|
872
|
-
return super().__mul__(other)
|
|
873
|
-
|
|
874
|
-
def inv(self):
|
|
875
|
-
"""
|
|
876
|
-
Compute the reciprocal compliance tensor
|
|
877
|
-
|
|
878
|
-
Returns
|
|
879
|
-
-------
|
|
880
|
-
ComplianceTensor
|
|
881
|
-
Reciprocal tensor
|
|
882
|
-
"""
|
|
883
|
-
C = np.linalg.inv(self.matrix)
|
|
884
|
-
return ComplianceTensor(C, symmetry=self.symmetry, phase_name=self.phase_name, orientations=self.orientations)
|
|
885
|
-
|
|
886
|
-
@property
|
|
887
|
-
def Young_modulus(self):
|
|
888
|
-
"""
|
|
889
|
-
Directional Young's modulus
|
|
890
|
-
|
|
891
|
-
Returns
|
|
892
|
-
-------
|
|
893
|
-
SphericalFunction
|
|
894
|
-
Young's modulus
|
|
895
|
-
"""
|
|
896
|
-
|
|
897
|
-
def compute_young_modulus(n):
|
|
898
|
-
eps = _compute_unit_strain_along_direction(self, n, n)
|
|
899
|
-
return 1 / eps
|
|
900
|
-
|
|
901
|
-
return SphericalFunction(compute_young_modulus)
|
|
902
|
-
|
|
903
|
-
@property
|
|
904
|
-
def shear_modulus(self):
|
|
905
|
-
"""
|
|
906
|
-
Directional shear modulus
|
|
907
|
-
|
|
908
|
-
Returns
|
|
909
|
-
-------
|
|
910
|
-
HyperSphericalFunction
|
|
911
|
-
Shear modulus
|
|
912
|
-
"""
|
|
913
|
-
|
|
914
|
-
def compute_shear_modulus(m, n):
|
|
915
|
-
eps = _compute_unit_strain_along_direction(self, m, n)
|
|
916
|
-
return 1 / (4 * eps)
|
|
917
|
-
|
|
918
|
-
return HyperSphericalFunction(compute_shear_modulus)
|
|
919
|
-
|
|
920
|
-
@property
|
|
921
|
-
def Poisson_ratio(self):
|
|
922
|
-
"""
|
|
923
|
-
Directional Poisson's ratio
|
|
924
|
-
|
|
925
|
-
Returns
|
|
926
|
-
-------
|
|
927
|
-
HyperSphericalFunction
|
|
928
|
-
Poisson's ratio
|
|
929
|
-
"""
|
|
930
|
-
|
|
931
|
-
def compute_PoissonRatio(m, n):
|
|
932
|
-
eps1 = _compute_unit_strain_along_direction(self, m, m)
|
|
933
|
-
eps2 = _compute_unit_strain_along_direction(self, m, n, direction='transverse')
|
|
934
|
-
return -eps2 / eps1
|
|
935
|
-
|
|
936
|
-
return HyperSphericalFunction(compute_PoissonRatio)
|
|
937
|
-
|
|
938
|
-
@property
|
|
939
|
-
def linear_compressibility(self):
|
|
940
|
-
"""
|
|
941
|
-
Compute the directional linear compressibility.
|
|
942
|
-
|
|
943
|
-
Returns
|
|
944
|
-
-------
|
|
945
|
-
SphericalFunction
|
|
946
|
-
Directional linear compressibility
|
|
947
|
-
|
|
948
|
-
See Also
|
|
949
|
-
--------
|
|
950
|
-
bulk_modulus : bulk modulus of the material
|
|
951
|
-
"""
|
|
952
|
-
|
|
953
|
-
def compute_linear_compressibility(n):
|
|
954
|
-
return _compute_unit_strain_along_direction(self, n, n, direction='spherical')
|
|
955
|
-
|
|
956
|
-
return SphericalFunction(compute_linear_compressibility)
|
|
957
|
-
|
|
958
|
-
@property
|
|
959
|
-
def bulk_modulus(self):
|
|
960
|
-
"""
|
|
961
|
-
Compute the bulk modulus of the material
|
|
962
|
-
|
|
963
|
-
Returns
|
|
964
|
-
-------
|
|
965
|
-
float
|
|
966
|
-
Bulk modulus
|
|
967
|
-
|
|
968
|
-
See Also
|
|
969
|
-
--------
|
|
970
|
-
linear_compressibility : directional linear compressibility
|
|
971
|
-
"""
|
|
972
|
-
return self.inv().bulk_modulus
|
|
973
|
-
|
|
974
|
-
def Voigt_average(self):
|
|
975
|
-
"""
|
|
976
|
-
Compute the Voigt average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
|
|
977
|
-
behaviour. Otherwise, the mean is computed over all orientations.
|
|
978
|
-
|
|
979
|
-
Returns
|
|
980
|
-
-------
|
|
981
|
-
StiffnessTensor
|
|
982
|
-
Voigt average of stiffness tensor
|
|
983
|
-
|
|
984
|
-
See Also
|
|
985
|
-
--------
|
|
986
|
-
Reuss_average : compute the Reuss average
|
|
987
|
-
Hill_average : compute the Voigt-Reuss-Hill average
|
|
988
|
-
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
989
|
-
"""
|
|
990
|
-
if self.orientations is None:
|
|
991
|
-
c = self.matrix
|
|
992
|
-
C11 = (c[0, 0] + c[1, 1] + c[2, 2]) / 5 \
|
|
993
|
-
+ (c[0, 1] + c[0, 2] + c[1, 2]) * 2 / 15 \
|
|
994
|
-
+ (c[3, 3] + c[4, 4] + c[5, 5]) * 4 / 15
|
|
995
|
-
C12 = (c[0, 0] + c[1, 1] + c[2, 2]) / 15 \
|
|
996
|
-
+ (c[0, 1] + c[0, 2] + c[1, 2]) * 4 / 15 \
|
|
997
|
-
- (c[3, 3] + c[4, 4] + c[5, 5]) * 2 / 15
|
|
998
|
-
C44 = (c[0, 0] + c[1, 1] + c[2, 2] - c[0, 1] - c[0, 2] - c[1, 2]) / 15 + (c[3, 3] + c[4, 4] + c[5, 5]) / 5
|
|
999
|
-
mat = _isotropic_matrix(C11, C12, C44)
|
|
1000
|
-
return StiffnessTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
1001
|
-
else:
|
|
1002
|
-
return StiffnessTensor(self.mean())
|
|
1003
|
-
|
|
1004
|
-
def Reuss_average(self):
|
|
1005
|
-
"""
|
|
1006
|
-
Compute the Reuss average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
|
|
1007
|
-
behaviour. Otherwise, the mean is computed over all orientations.
|
|
1008
|
-
|
|
1009
|
-
Returns
|
|
1010
|
-
-------
|
|
1011
|
-
StiffnessTensor
|
|
1012
|
-
Reuss average of stiffness tensor
|
|
1013
|
-
|
|
1014
|
-
See Also
|
|
1015
|
-
--------
|
|
1016
|
-
Voigt_average : compute the Voigt average
|
|
1017
|
-
Hill_average : compute the Voigt-Reuss-Hill average
|
|
1018
|
-
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
1019
|
-
"""
|
|
1020
|
-
return self.inv().Reuss_average().inv()
|
|
1021
|
-
|
|
1022
|
-
def Hill_average(self):
|
|
1023
|
-
"""
|
|
1024
|
-
Compute the (Voigt-Reuss-)Hill average of the stiffness tensor. If the tensor contains no orientation, we assume
|
|
1025
|
-
isotropic behaviour. Otherwise, the mean is computed over all orientations.
|
|
1026
|
-
|
|
1027
|
-
Returns
|
|
1028
|
-
-------
|
|
1029
|
-
StiffnessTensor
|
|
1030
|
-
Voigt-Reuss-Hill average of tensor
|
|
1031
|
-
|
|
1032
|
-
See Also
|
|
1033
|
-
--------
|
|
1034
|
-
Voigt_average : compute the Voigt average
|
|
1035
|
-
Reuss_average : compute the Reuss average
|
|
1036
|
-
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
1037
|
-
"""
|
|
1038
|
-
Reuss = self.Reuss_average()
|
|
1039
|
-
Voigt = self.Voigt_average()
|
|
1040
|
-
return (Reuss + Voigt) * 0.5
|
|
1041
|
-
|
|
1042
|
-
def average(self, method):
|
|
1043
|
-
"""
|
|
1044
|
-
Compute either the Voigt, Reuss, or Hill average of the stiffness tensor.
|
|
1045
|
-
|
|
1046
|
-
This function is just a shortcut for Voigt_average(), Reuss_average(), or Hill_average() and Hill_average().
|
|
1047
|
-
|
|
1048
|
-
Parameters
|
|
1049
|
-
----------
|
|
1050
|
-
method : str {'Voigt', 'Reuss', 'Hill'}
|
|
1051
|
-
Method to use to compute the average.
|
|
1052
|
-
|
|
1053
|
-
Returns
|
|
1054
|
-
-------
|
|
1055
|
-
StiffnessTensor
|
|
1056
|
-
|
|
1057
|
-
See Also
|
|
1058
|
-
--------
|
|
1059
|
-
Voigt_average : compute the Voigt average
|
|
1060
|
-
Reuss_average : compute the Reuss average
|
|
1061
|
-
Hill_average : compute the Voigt-Reuss-Hill average
|
|
1062
|
-
"""
|
|
1063
|
-
method = method.capitalize()
|
|
1064
|
-
if method in ('Voigt', 'Reuss', 'Hill'):
|
|
1065
|
-
fun = getattr(self, method + '_average')
|
|
1066
|
-
return fun()
|
|
1067
|
-
else:
|
|
1068
|
-
raise NotImplementedError('Only Voigt, Reus, and Hill are implemented.')
|
|
1069
|
-
|
|
1070
|
-
@classmethod
|
|
1071
|
-
def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
|
|
1072
|
-
"""
|
|
1073
|
-
Create an isotropic stiffness tensor from two elasticity coefficients, namely: E, nu, lame1, or lame2. Exactly
|
|
1074
|
-
two of these coefficients must be provided.
|
|
1075
|
-
|
|
1076
|
-
Parameters
|
|
1077
|
-
----------
|
|
1078
|
-
E : float, None
|
|
1079
|
-
Young modulus
|
|
1080
|
-
nu : float, None
|
|
1081
|
-
Poisson ratio
|
|
1082
|
-
lame1 : float, None
|
|
1083
|
-
First Lamé coefficient
|
|
1084
|
-
lame2 : float, None
|
|
1085
|
-
Second Lamé coefficient
|
|
1086
|
-
phase_name : str, None
|
|
1087
|
-
Name to print
|
|
1088
|
-
|
|
1089
|
-
Returns
|
|
1090
|
-
-------
|
|
1091
|
-
Corresponding isotropic stiffness tensor
|
|
1092
|
-
|
|
1093
|
-
See Also
|
|
1094
|
-
--------
|
|
1095
|
-
transverse_isotropic : create a transverse-isotropic tensor
|
|
1096
|
-
|
|
1097
|
-
Examples
|
|
1098
|
-
--------
|
|
1099
|
-
On can check that the shear modulus for steel is around 82 GPa:
|
|
1100
|
-
|
|
1101
|
-
>>> from Elasticipy.FourthOrderTensor import StiffnessTensor
|
|
1102
|
-
>>> C=StiffnessTensor.isotropic(E=210e3, nu=0.28)
|
|
1103
|
-
>>> C.shear_modulus
|
|
1104
|
-
Hyperspherical function
|
|
1105
|
-
Min=82031.24999999997, Max=82031.25000000006
|
|
1106
|
-
"""
|
|
1107
|
-
argument_vector = np.array([E, nu, lame1, lame2])
|
|
1108
|
-
if np.count_nonzero(argument_vector) != 2:
|
|
1109
|
-
raise ValueError("Exactly two values are required among E, nu, lame1 and lame2.")
|
|
1110
|
-
if E is not None:
|
|
1111
|
-
if nu is not None:
|
|
1112
|
-
lame1 = E * nu / ((1 + nu) * (1 - 2 * nu))
|
|
1113
|
-
lame2 = E / (1 + nu) / 2
|
|
1114
|
-
elif lame1 is not None:
|
|
1115
|
-
R = np.sqrt(E ** 2 + 9 * lame1 ** 2 + 2 * E * lame1)
|
|
1116
|
-
lame2 = (E - 3 * lame1 + R) / 4
|
|
1117
|
-
elif lame2 is not None:
|
|
1118
|
-
lame1 = lame2 * (E - 2 * lame2) / (3 * lame2 - E)
|
|
1119
|
-
else:
|
|
1120
|
-
raise ValueError('Either nu, lame1 or lame2 must be provided.')
|
|
1121
|
-
elif nu is not None:
|
|
1122
|
-
if lame1 is not None:
|
|
1123
|
-
lame2 = lame1 * (1 - 2 * nu) / (2 * nu)
|
|
1124
|
-
elif lame2 is not None:
|
|
1125
|
-
lame1 = 2 * lame2 * nu / (1 - 2 * nu)
|
|
1126
|
-
else:
|
|
1127
|
-
raise ValueError('Either lame1 or lame2 must be provided.')
|
|
1128
|
-
C11 = lame1 + 2 * lame2
|
|
1129
|
-
C12 = lame1
|
|
1130
|
-
C44 = lame2
|
|
1131
|
-
matrix = _isotropic_matrix(C11, C12, C44)
|
|
1132
|
-
return StiffnessTensor(np.array(matrix), symmetry='isotropic', phase_name=phase_name)
|
|
1133
|
-
|
|
1134
|
-
@classmethod
|
|
1135
|
-
def orthotropic(cls, *, Ex, Ey, Ez, nu_yx, nu_zx, nu_zy, Gxy, Gxz, Gyz, **kwargs):
|
|
1136
|
-
"""
|
|
1137
|
-
Create a stiffness tensor corresponding to orthotropic symmetry, given the engineering constants.
|
|
1138
|
-
|
|
1139
|
-
Parameters
|
|
1140
|
-
----------
|
|
1141
|
-
Ex : float
|
|
1142
|
-
Young modulus along the x axis
|
|
1143
|
-
Ey : float
|
|
1144
|
-
Young modulus along the y axis
|
|
1145
|
-
Ez : float
|
|
1146
|
-
Young modulus along the z axis
|
|
1147
|
-
nu_yx : float
|
|
1148
|
-
Poisson ratio between x and y axes
|
|
1149
|
-
nu_zx : float
|
|
1150
|
-
Poisson ratio between x and z axes
|
|
1151
|
-
nu_zy : float
|
|
1152
|
-
Poisson ratio between y and z axes
|
|
1153
|
-
Gxy : float
|
|
1154
|
-
Shear modulus in the x-y plane
|
|
1155
|
-
Gxz : float
|
|
1156
|
-
Shear modulus in the x-z plane
|
|
1157
|
-
Gyz : float
|
|
1158
|
-
Shear modulus in the y-z plane
|
|
1159
|
-
kwargs : dict, optional
|
|
1160
|
-
Keyword arguments to pass to the StiffnessTensor constructor
|
|
1161
|
-
|
|
1162
|
-
Returns
|
|
1163
|
-
-------
|
|
1164
|
-
StiffnessTensor
|
|
1165
|
-
|
|
1166
|
-
See Also
|
|
1167
|
-
--------
|
|
1168
|
-
transverse_isotropic : create a stiffness tensor for transverse-isotropic symmetry
|
|
1169
|
-
"""
|
|
1170
|
-
tri_sup = np.array([[1 / Ex, -nu_yx / Ey, -nu_zx / Ez, 0, 0, 0],
|
|
1171
|
-
[0, 1 / Ey, -nu_zy / Ez, 0, 0, 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]])
|
|
1176
|
-
S = tri_sup + np.tril(tri_sup.T, -1)
|
|
1177
|
-
return StiffnessTensor(np.linalg.inv(S), symmetry='orthotropic', **kwargs)
|
|
1178
|
-
|
|
1179
|
-
@classmethod
|
|
1180
|
-
def transverse_isotropic(cls, *, Ex, Ez, nu_yx, nu_zx, Gxz, **kwargs):
|
|
1181
|
-
"""
|
|
1182
|
-
Create a stiffness tensor corresponding to the transverse isotropic symmetry, given the engineering constants.
|
|
1183
|
-
|
|
1184
|
-
Parameters
|
|
1185
|
-
----------
|
|
1186
|
-
Ex : float
|
|
1187
|
-
Young modulus along the x axis
|
|
1188
|
-
Ez : float
|
|
1189
|
-
Young modulus along the y axis
|
|
1190
|
-
nu_yx : float
|
|
1191
|
-
Poisson ratio between x and y axes
|
|
1192
|
-
nu_zx : float
|
|
1193
|
-
Poisson ratio between x and z axes
|
|
1194
|
-
Gxz : float
|
|
1195
|
-
Shear modulus in the x-z plane
|
|
1196
|
-
kwargs : dict
|
|
1197
|
-
Keyword arguments to pass to the StiffnessTensor constructor
|
|
1198
|
-
|
|
1199
|
-
Returns
|
|
1200
|
-
-------
|
|
1201
|
-
StiffnessTensor
|
|
1202
|
-
|
|
1203
|
-
See Also
|
|
1204
|
-
--------
|
|
1205
|
-
orthotropic : create a stiffness tensor for orthotropic symmetry
|
|
1206
|
-
"""
|
|
1207
|
-
Gxy = Ex / (2 * (1 + nu_yx))
|
|
1208
|
-
C = StiffnessTensor.orthotropic(Ex=Ex, Ey=Ex, Ez=Ez,
|
|
1209
|
-
nu_yx=nu_yx, nu_zx=nu_zx, nu_zy=nu_zx,
|
|
1210
|
-
Gxy=Gxy, Gxz=Gxz, Gyz=Gxz, **kwargs)
|
|
1211
|
-
C.symmetry = 'transverse-isotropic'
|
|
1212
|
-
return C
|
|
1213
|
-
|
|
1214
|
-
def Christoffel_tensor(self, u):
|
|
1215
|
-
"""
|
|
1216
|
-
Create the Christoffel tensor along a given direction, or set or directions.
|
|
1217
|
-
|
|
1218
|
-
Parameters
|
|
1219
|
-
----------
|
|
1220
|
-
u : list or np.ndarray
|
|
1221
|
-
3D direction(s) to compute the Christoffel tensor along with
|
|
1222
|
-
|
|
1223
|
-
Returns
|
|
1224
|
-
-------
|
|
1225
|
-
Gamma : np.ndarray
|
|
1226
|
-
Array of Christoffel tensor(s). if u is a list of directions, Gamma[i] is the Christoffel tensor for
|
|
1227
|
-
direction u[i].
|
|
1228
|
-
|
|
1229
|
-
See Also
|
|
1230
|
-
--------
|
|
1231
|
-
wave_velocity : computes the p- and s-wave velocities.
|
|
1232
|
-
|
|
1233
|
-
Notes
|
|
1234
|
-
-----
|
|
1235
|
-
For a given stiffness tensor **C** and a given unit vector **u**, the Christoffel tensor is defined as [2]_ :
|
|
1236
|
-
|
|
1237
|
-
.. math:: M_{ij} = C_{iklj}.u_k.u_l
|
|
1238
|
-
|
|
1239
|
-
"""
|
|
1240
|
-
u_vec = np.atleast_2d(u)
|
|
1241
|
-
u_vec = (u_vec.T / np.linalg.norm(u_vec, axis=1)).T
|
|
1242
|
-
return np.einsum('inmj,pn,pm->pij', self.full_tensor(), u_vec, u_vec)
|
|
1243
|
-
|
|
1244
|
-
def wave_velocity(self, rho):
|
|
1245
|
-
"""
|
|
1246
|
-
Compute the wave velocities, given the mass density.
|
|
1247
|
-
|
|
1248
|
-
Parameters
|
|
1249
|
-
----------
|
|
1250
|
-
rho : float
|
|
1251
|
-
mass density. Its unit must be consistent with that of the stiffness tensor. See notes for hints.
|
|
1252
|
-
|
|
1253
|
-
See Also
|
|
1254
|
-
--------
|
|
1255
|
-
ChristoffelTensor : Computes the Christoffel tensor along a given direction
|
|
1256
|
-
|
|
1257
|
-
Returns
|
|
1258
|
-
-------
|
|
1259
|
-
c_p : SphericalFunction
|
|
1260
|
-
Velocity of the primary (compressive) wave
|
|
1261
|
-
c_s1 : SphericalFunction
|
|
1262
|
-
Velocity of the fast secondary (shear) wave
|
|
1263
|
-
c_s2 : SphericalFunction
|
|
1264
|
-
Velocity of the slow secondary (shear) wave
|
|
1265
|
-
|
|
1266
|
-
Notes
|
|
1267
|
-
-----
|
|
1268
|
-
The estimation of the wave velocities is made by finding the eigenvalues of the Christoffel tensor [2]_.
|
|
1269
|
-
|
|
1270
|
-
One should double-check the units. The table below provides hints about the unit you get, depending on the units
|
|
1271
|
-
you use for stiffness and the mass density:
|
|
1272
|
-
|
|
1273
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1274
|
-
| Stiffness | Mass density | Velocities | Notes |
|
|
1275
|
-
+=================+==============+============+=======================+
|
|
1276
|
-
| Pa (N/m²) | kg/m³ | m/s | SI units |
|
|
1277
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1278
|
-
| GPa (10⁹ Pa) | kg/dm³ | km/s | Conversion factor |
|
|
1279
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1280
|
-
| GPa (10³ N/mm²) | kg/mm³ | m/s | Consistent units |
|
|
1281
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1282
|
-
| MPa (10⁶ Pa) | kg/m³ | km/s | Conversion factor |
|
|
1283
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1284
|
-
| MPa (10³ N/mm²) | g/mm³ | m/s | Consistent units |
|
|
1285
|
-
+-----------------+--------------+------------+-----------------------+
|
|
1286
|
-
|
|
1287
|
-
References
|
|
1288
|
-
----------
|
|
1289
|
-
.. [2] J. W. Jaeken, S. Cottenier, Solving the Christoffel equation: Phase and group velocities, Computer Physics
|
|
1290
|
-
Communications (207), 2016, https://doi.org/10.1016/j.cpc.2016.06.014.
|
|
1291
|
-
|
|
1292
|
-
"""
|
|
1293
|
-
|
|
1294
|
-
def make_fun(index):
|
|
1295
|
-
def fun(n):
|
|
1296
|
-
Gamma = self.Christoffel_tensor(n)
|
|
1297
|
-
eig, _ = np.linalg.eig(Gamma)
|
|
1298
|
-
if index == 0:
|
|
1299
|
-
eig_of_interest = np.max(eig, axis=-1)
|
|
1300
|
-
elif index == 1:
|
|
1301
|
-
eig_of_interest = np.median(eig, axis=-1)
|
|
1302
|
-
else:
|
|
1303
|
-
eig_of_interest = np.min(eig, axis=-1)
|
|
1304
|
-
return np.sqrt(eig_of_interest / rho)
|
|
1305
|
-
|
|
1306
|
-
return fun
|
|
1307
|
-
|
|
1308
|
-
return [SphericalFunction(make_fun(i)) for i in range(3)]
|
|
1309
|
-
|
|
1310
|
-
@classmethod
|
|
1311
|
-
def from_MP(cls, ids, api_key=None):
|
|
1312
|
-
"""
|
|
1313
|
-
Import stiffness tensor(s) from the Materials Project API, given their material ids.
|
|
1314
|
-
|
|
1315
|
-
You need to register to `<https://materialsproject.org>`_ first to get an API key. This key can be explicitly
|
|
1316
|
-
passed as an argument (see below), or provided as an environment variable named MP_API_KEY.
|
|
1317
|
-
|
|
1318
|
-
Parameters
|
|
1319
|
-
----------
|
|
1320
|
-
ids : str or list of str
|
|
1321
|
-
ID(s) of the material to import (e.g. "mp-1048")
|
|
1322
|
-
api_key : str, optional
|
|
1323
|
-
API key to the Materials Project API. If not provided, it should be available as the API_KEY environment
|
|
1324
|
-
variable.
|
|
1325
|
-
|
|
1326
|
-
Returns
|
|
1327
|
-
-------
|
|
1328
|
-
list of StiffnessTensor
|
|
1329
|
-
If one of the requested material ids was not found, the corresponding value in the list will be None.
|
|
1330
|
-
"""
|
|
1331
|
-
try:
|
|
1332
|
-
from mp_api.client import MPRester
|
|
1333
|
-
except ImportError:
|
|
1334
|
-
raise ModuleNotFoundError('mp_api module is required for this function.')
|
|
1335
|
-
if type(ids) is str:
|
|
1336
|
-
Cdict = dict.fromkeys([ids])
|
|
1337
|
-
else:
|
|
1338
|
-
Cdict = dict.fromkeys(ids)
|
|
1339
|
-
with MPRester(api_key=api_key) as mpr:
|
|
1340
|
-
elasticity_doc = mpr.materials.elasticity.search(material_ids=ids)
|
|
1341
|
-
for material in elasticity_doc:
|
|
1342
|
-
key = str(material.material_id)
|
|
1343
|
-
if material.elastic_tensor is not None:
|
|
1344
|
-
matrix = material.elastic_tensor.ieee_format
|
|
1345
|
-
symmetry = material.symmetry.crystal_system.value
|
|
1346
|
-
phase_name = material.formula_pretty
|
|
1347
|
-
C = StiffnessTensor(matrix, symmetry=symmetry, phase_name=phase_name)
|
|
1348
|
-
else:
|
|
1349
|
-
C = None
|
|
1350
|
-
Cdict[key] = C
|
|
1351
|
-
if elasticity_doc:
|
|
1352
|
-
if isinstance(ids, str):
|
|
1353
|
-
return C
|
|
1354
|
-
else:
|
|
1355
|
-
return [Cdict[id] for id in ids]
|
|
1356
|
-
else:
|
|
1357
|
-
return None
|
|
1358
|
-
|
|
1359
|
-
@classmethod
|
|
1360
|
-
def weighted_average(cls, Cs, volume_fractions, method):
|
|
1361
|
-
"""
|
|
1362
|
-
Compute the weighted average of a list of stiffness tensors, with respect to a given method (Voigt, Reuss or
|
|
1363
|
-
Hill).
|
|
1364
|
-
|
|
1365
|
-
Parameters
|
|
1366
|
-
----------
|
|
1367
|
-
Cs : list of StiffnessTensor or list of ComplianceTensor or tuple of StiffnessTensor or tuple of ComplianceTensor
|
|
1368
|
-
Series of tensors to compute the average from
|
|
1369
|
-
volume_fractions : iterable of floats
|
|
1370
|
-
Volume fractions of each phase
|
|
1371
|
-
method : str, {'Voigt', 'Reuss', 'Hill'}
|
|
1372
|
-
Method to use. It can be 'Voigt', 'Reuss', or 'Hill'.
|
|
1373
|
-
|
|
1374
|
-
Returns
|
|
1375
|
-
-------
|
|
1376
|
-
StiffnessTensor
|
|
1377
|
-
Average tensor
|
|
1378
|
-
"""
|
|
1379
|
-
if np.all([isinstance(a, ComplianceTensor) for a in Cs]):
|
|
1380
|
-
Cs = [C.inv() for C in Cs]
|
|
1381
|
-
if np.all([isinstance(a, StiffnessTensor) for a in Cs]):
|
|
1382
|
-
C_stack = np.array([C.matrix for C in Cs])
|
|
1383
|
-
method = method.capitalize()
|
|
1384
|
-
if method == 'Voigt':
|
|
1385
|
-
C_avg = np.average(C_stack, weights=volume_fractions, axis=0)
|
|
1386
|
-
return StiffnessTensor(C_avg)
|
|
1387
|
-
elif method == 'Reuss':
|
|
1388
|
-
S_stack = np.linalg.inv(C_stack)
|
|
1389
|
-
S_avg = np.average(S_stack, weights=volume_fractions, axis=0)
|
|
1390
|
-
return StiffnessTensor(np.linalg.inv(S_avg))
|
|
1391
|
-
elif method == 'Hill':
|
|
1392
|
-
C_voigt = cls.weighted_average(Cs, volume_fractions, 'Voigt')
|
|
1393
|
-
C_reuss = cls.weighted_average(Cs, volume_fractions, 'Reuss')
|
|
1394
|
-
return (C_voigt + C_reuss) * 0.5
|
|
1395
|
-
else:
|
|
1396
|
-
raise ValueError('Method must be either Voigt, Reuss or Hill.')
|
|
1397
|
-
else:
|
|
1398
|
-
raise ValueError('The first argument must be either a list of ComplianceTensors or '
|
|
1399
|
-
'a list of StiffnessTensor.')
|
|
1400
|
-
|
|
1401
|
-
@property
|
|
1402
|
-
def universal_anisotropy(self):
|
|
1403
|
-
"""
|
|
1404
|
-
Compute the universal anisotropy factor.
|
|
1405
|
-
|
|
1406
|
-
The larger the value, the more likely the material will behave in an anisotropic way.
|
|
1407
|
-
|
|
1408
|
-
Returns
|
|
1409
|
-
-------
|
|
1410
|
-
float
|
|
1411
|
-
The universal anisotropy factor.
|
|
1412
|
-
|
|
1413
|
-
Notes
|
|
1414
|
-
-----
|
|
1415
|
-
The universal anisotropy factor is defined as [3]_:
|
|
1416
|
-
|
|
1417
|
-
.. math::
|
|
1418
|
-
|
|
1419
|
-
5\\frac{G_v}{G_r} + \\frac{K_v}{K_r} - 6
|
|
1420
|
-
|
|
1421
|
-
References
|
|
1422
|
-
----------
|
|
1423
|
-
.. [3] S. I. Ranganathan and M. Ostoja-Starzewski, Universal Elastic Anisotropy Index,
|
|
1424
|
-
*Phys. Rev. Lett.*, 101(5), 055504, 2008. https://doi.org/10.1103/PhysRevLett.101.055504
|
|
1425
|
-
"""
|
|
1426
|
-
C = self._unrotate() # Ensure that the averages do not use the orientations
|
|
1427
|
-
Cvoigt = C.Voigt_average()
|
|
1428
|
-
Creuss = C.Reuss_average()
|
|
1429
|
-
Gv = Cvoigt.matrix[3, 3]
|
|
1430
|
-
Gr = Creuss.matrix[3, 3]
|
|
1431
|
-
Kv = Cvoigt.bulk_modulus
|
|
1432
|
-
Kr = Creuss.bulk_modulus
|
|
1433
|
-
return 5 * Gv / Gr + Kv / Kr - 6
|
|
1434
|
-
|
|
1435
|
-
@property
|
|
1436
|
-
def Zener_ratio(self):
|
|
1437
|
-
"""
|
|
1438
|
-
Compute the Zener ratio (Z). Only valid for cubic symmetry.
|
|
1439
|
-
|
|
1440
|
-
It is only valid for cubic and isotropic symmetry. Will return NaN for other symmetries.
|
|
1441
|
-
|
|
1442
|
-
Returns
|
|
1443
|
-
-------
|
|
1444
|
-
float
|
|
1445
|
-
Zener ratio (NaN is the symmetry is not cubic)
|
|
1446
|
-
|
|
1447
|
-
Notes
|
|
1448
|
-
-----
|
|
1449
|
-
The Zener ratio is defined as:
|
|
1450
|
-
|
|
1451
|
-
.. math::
|
|
1452
|
-
|
|
1453
|
-
Z=\\frac{ 2C_{44} }{C11 - C12}
|
|
1454
|
-
|
|
1455
|
-
See Also
|
|
1456
|
-
--------
|
|
1457
|
-
universal_anisotropy : compute the universal anisotropy factor
|
|
1458
|
-
"""
|
|
1459
|
-
if self.symmetry == 'isotropic':
|
|
1460
|
-
return 1.0
|
|
1461
|
-
elif self.symmetry == 'cubic':
|
|
1462
|
-
return 2 * self.C44 / (self.C11 - self.C12)
|
|
1463
|
-
else:
|
|
1464
|
-
return np.nan
|
|
1465
|
-
|
|
1466
|
-
def to_pymatgen(self):
|
|
1467
|
-
"""
|
|
1468
|
-
Convert the stiffness tensor (from Elasticipy) to Python Materials Genomics (Pymatgen) format.
|
|
1469
|
-
|
|
1470
|
-
Returns
|
|
1471
|
-
-------
|
|
1472
|
-
pymatgen.analysis.elasticity.elastic.ElasticTensor
|
|
1473
|
-
Stiffness tensor for pymatgen
|
|
1474
|
-
"""
|
|
1475
|
-
try:
|
|
1476
|
-
from pymatgen.analysis.elasticity import elastic as matgenElast
|
|
1477
|
-
except ImportError:
|
|
1478
|
-
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
1479
|
-
return matgenElast.ElasticTensor(self.full_tensor())
|
|
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
|
-
|
|
1598
|
-
class ComplianceTensor(StiffnessTensor):
|
|
1599
|
-
"""
|
|
1600
|
-
Class for manipulating compliance tensors
|
|
1601
|
-
"""
|
|
1602
|
-
tensor_name = 'Compliance'
|
|
1603
|
-
voigt_map = np.array([[1., 1., 1., 2., 2., 2.],
|
|
1604
|
-
[1., 1., 1., 2., 2., 2.],
|
|
1605
|
-
[1., 1., 1., 2., 2., 2.],
|
|
1606
|
-
[2., 2., 2., 4., 4., 4.],
|
|
1607
|
-
[2., 2., 2., 4., 4., 4.],
|
|
1608
|
-
[2., 2., 2., 4., 4., 4.]])
|
|
1609
|
-
C11_C12_factor = 2.0
|
|
1610
|
-
component_prefix = 'S'
|
|
1611
|
-
C46_C56_factor = 2.0
|
|
1612
|
-
|
|
1613
|
-
def __init__(self, C, check_positive_definite=True, **kwargs):
|
|
1614
|
-
super().__init__(C, check_positive_definite=check_positive_definite, **kwargs)
|
|
1615
|
-
|
|
1616
|
-
def __mul__(self, other):
|
|
1617
|
-
if isinstance(other, StressTensor):
|
|
1618
|
-
return StrainTensor(self * other.matrix)
|
|
1619
|
-
elif isinstance(other, StrainTensor):
|
|
1620
|
-
raise ValueError('You cannot multiply a compliance tensor with Strain tensor.')
|
|
1621
|
-
else:
|
|
1622
|
-
return super().__mul__(other)
|
|
1623
|
-
|
|
1624
|
-
def inv(self):
|
|
1625
|
-
"""
|
|
1626
|
-
Compute the reciprocal stiffness tensor
|
|
1627
|
-
|
|
1628
|
-
Returns
|
|
1629
|
-
-------
|
|
1630
|
-
StiffnessTensor
|
|
1631
|
-
Reciprocal tensor
|
|
1632
|
-
"""
|
|
1633
|
-
S = np.linalg.inv(self.matrix)
|
|
1634
|
-
return StiffnessTensor(S, symmetry=self.symmetry, phase_name=self.phase_name, orientations=self.orientations)
|
|
1635
|
-
|
|
1636
|
-
def Reuss_average(self):
|
|
1637
|
-
if self.orientations is None:
|
|
1638
|
-
s = self.matrix
|
|
1639
|
-
S11 = (s[0, 0] + s[1, 1] + s[2, 2]) / 5 \
|
|
1640
|
-
+ (s[0, 1] + s[0, 2] + s[1, 2]) * 2 / 15 \
|
|
1641
|
-
+ (s[3, 3] + s[4, 4] + s[5, 5]) * 1 / 15
|
|
1642
|
-
S12 = (s[0, 0] + s[1, 1] + s[2, 2]) / 15 \
|
|
1643
|
-
+ (s[0, 1] + s[0, 2] + s[1, 2]) * 4 / 15 \
|
|
1644
|
-
- (s[3, 3] + s[4, 4] + s[5, 5]) * 1 / 30
|
|
1645
|
-
S44 = ((s[0, 0] + s[1, 1] + s[2, 2] - s[0, 1] - s[0, 2] - s[1, 2]) * 4 / 15 +
|
|
1646
|
-
(s[3, 3] + s[4, 4] + s[5, 5]) / 5)
|
|
1647
|
-
mat = _isotropic_matrix(S11, S12, S44)
|
|
1648
|
-
return ComplianceTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
1649
|
-
else:
|
|
1650
|
-
return ComplianceTensor(self.mean())
|
|
1651
|
-
|
|
1652
|
-
def Voigt_average(self):
|
|
1653
|
-
return self.inv().Voigt_average().inv()
|
|
1654
|
-
|
|
1655
|
-
def Hill_average(self):
|
|
1656
|
-
return self.inv().Hill_average().inv()
|
|
1657
|
-
|
|
1658
|
-
@classmethod
|
|
1659
|
-
def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
|
|
1660
|
-
return super().isotropic(E=E, nu=nu, lame1=lame1, lame2=lame2, phase_name=None).inv()
|
|
1661
|
-
|
|
1662
|
-
@classmethod
|
|
1663
|
-
def orthotropic(cls, *args, **kwargs):
|
|
1664
|
-
return super().orthotropic(*args, **kwargs).inv()
|
|
1665
|
-
|
|
1666
|
-
@classmethod
|
|
1667
|
-
def transverse_isotropic(cls, *args, **kwargs):
|
|
1668
|
-
return super().transverse_isotropic(*args, **kwargs).inv()
|
|
1669
|
-
|
|
1670
|
-
@classmethod
|
|
1671
|
-
def weighted_average(cls, *args):
|
|
1672
|
-
return super().weighted_average(*args).inv()
|
|
1673
|
-
|
|
1674
|
-
@property
|
|
1675
|
-
def bulk_modulus(self):
|
|
1676
|
-
return 1 / np.sum(self.matrix[0:3, 0:3])
|
|
1677
|
-
|
|
1678
|
-
@property
|
|
1679
|
-
def universal_anisotropy(self):
|
|
1680
|
-
"""
|
|
1681
|
-
Compute the universal anisotropy factor.
|
|
1682
|
-
|
|
1683
|
-
It is actually an alias for inv().universal_anisotropy.
|
|
1684
|
-
|
|
1685
|
-
Returns
|
|
1686
|
-
-------
|
|
1687
|
-
float
|
|
1688
|
-
Universal anisotropy factor
|
|
1689
|
-
"""
|
|
1690
|
-
return self.inv().universal_anisotropy
|
|
1691
|
-
|
|
1692
|
-
def to_pymatgen(self):
|
|
1693
|
-
"""
|
|
1694
|
-
Convert the compliance tensor (from Elasticipy) to Python Materials Genomics (Pymatgen) format.
|
|
1695
|
-
|
|
1696
|
-
Returns
|
|
1697
|
-
-------
|
|
1698
|
-
pymatgen.analysis.elasticity.elastic.Compliance
|
|
1699
|
-
Compliance tensor for pymatgen
|
|
1700
|
-
"""
|
|
1701
|
-
try:
|
|
1702
|
-
from pymatgen.analysis.elasticity import elastic as matgenElast
|
|
1703
|
-
except ImportError:
|
|
1704
|
-
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
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
|
|
1
|
+
import warnings
|
|
2
|
+
from Elasticipy.tensors.elasticity import StiffnessTensor as NewStiffnessTensor
|
|
3
|
+
from Elasticipy.tensors.elasticity import ComplianceTensor as NewComplianceTensor
|
|
4
|
+
|
|
5
|
+
warnings.warn(
|
|
6
|
+
"The module 'Elasticipy.FourthOrderTensor' is deprecated and will be removed in a future release. "
|
|
7
|
+
"Please use 'Elasticipy.tensors.elasticity' instead.",
|
|
8
|
+
DeprecationWarning,
|
|
9
|
+
stacklevel=2
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class StiffnessTensor(NewStiffnessTensor):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class ComplianceTensor(NewComplianceTensor):
|
|
16
|
+
pass
|