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
|
@@ -0,0 +1,1748 @@
|
|
|
1
|
+
from Elasticipy.tensors.fourth_order import SymmetricFourthOrderTensor
|
|
2
|
+
from Elasticipy.spherical_function import SphericalFunction, HyperSphericalFunction
|
|
3
|
+
from Elasticipy.crystal_symmetries import SYMMETRIES
|
|
4
|
+
from Elasticipy.tensors.stress_strain import StrainTensor, StressTensor
|
|
5
|
+
from Elasticipy.tensors.mapping import VoigtMapping, KelvinMapping
|
|
6
|
+
import numpy as np
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
def _parse_tensor_components(prefix, **kwargs):
|
|
10
|
+
pattern = r'^{}(\d{{2}})$'.format(prefix)
|
|
11
|
+
value = dict()
|
|
12
|
+
for k, v in kwargs.items():
|
|
13
|
+
match = re.match(pattern, k) # Extract 'C11' to '11' and so
|
|
14
|
+
if match:
|
|
15
|
+
value[match.group(1)] = v
|
|
16
|
+
return value
|
|
17
|
+
|
|
18
|
+
def _indices2str(ij):
|
|
19
|
+
return f'{ij[0] + 1}{ij[1] + 1}'
|
|
20
|
+
|
|
21
|
+
def _isotropic_matrix(C11, C12, C44):
|
|
22
|
+
return np.array([[C11, C12, C12, 0, 0, 0],
|
|
23
|
+
[C12, C11, C12, 0, 0, 0],
|
|
24
|
+
[C12, C12, C11, 0, 0, 0],
|
|
25
|
+
[0, 0, 0, C44, 0, 0],
|
|
26
|
+
[0, 0, 0, 0, C44, 0],
|
|
27
|
+
[0, 0, 0, 0, 0, C44]])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_definite_positive(mat):
|
|
31
|
+
try:
|
|
32
|
+
np.linalg.cholesky(mat)
|
|
33
|
+
except np.linalg.LinAlgError:
|
|
34
|
+
eigen_val = np.linalg.eigvals(mat)
|
|
35
|
+
raise ValueError('The input matrix is not definite positive (eigenvalues: {})'.format(eigen_val))
|
|
36
|
+
|
|
37
|
+
def _switch_poisson_ratios(nu_xy, nu_yx, Ex, Ey, indices):
|
|
38
|
+
if nu_yx is None and nu_xy is not None:
|
|
39
|
+
return nu_xy * Ey / Ex
|
|
40
|
+
elif nu_yx is not None and nu_xy is None:
|
|
41
|
+
return nu_yx
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError('Either nu_{0}{1} or nu_{1}{0} must be provided'.format(*indices))
|
|
44
|
+
|
|
45
|
+
class StiffnessTensor(SymmetricFourthOrderTensor):
|
|
46
|
+
"""
|
|
47
|
+
Class for manipulating fourth-order stiffness tensors.
|
|
48
|
+
"""
|
|
49
|
+
tensor_name = 'Stiffness'
|
|
50
|
+
C11_C12_factor = 0.5
|
|
51
|
+
C46_C56_factor = 1.0
|
|
52
|
+
component_prefix = 'C'
|
|
53
|
+
|
|
54
|
+
def __init__(self, M, symmetry='Triclinic', check_positive_definite=True, phase_name= None, mapping=VoigtMapping(), **kwargs):
|
|
55
|
+
"""
|
|
56
|
+
Construct of stiffness tensor from a (6,6) matrix.
|
|
57
|
+
|
|
58
|
+
The input matrix must be symmetric, otherwise an error is thrown (except if check_symmetry==False, see below)
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
M : np.ndarray
|
|
63
|
+
(6,6) matrix corresponding to the stiffness tensor, written using the Voigt notation, or array of shape
|
|
64
|
+
(3,3,3,3).
|
|
65
|
+
phase_name : str, default None
|
|
66
|
+
Name to display
|
|
67
|
+
symmetry : str, default Triclinic
|
|
68
|
+
Name of the crystal's symmetry
|
|
69
|
+
check_symmetry : bool, optional
|
|
70
|
+
Whether to check or not that the input matrix is symmetric.
|
|
71
|
+
force_symmetry : bool, optional
|
|
72
|
+
If true, the major symmetry of the tensor is forces
|
|
73
|
+
mapping : str or MappingConvention
|
|
74
|
+
mapping convention to use. Default is VoigtMapping.
|
|
75
|
+
|
|
76
|
+
Notes
|
|
77
|
+
-----
|
|
78
|
+
The units used when building the stiffness tensor are up to the user (GPa, MPa, psi etc.). Therefor, the
|
|
79
|
+
results you will get when performing operations (Young's modulus, "product" with strain tensor etc.) will be
|
|
80
|
+
consistent with these units. For instance, if the stiffness tensor is defined in GPa, the computed stress will
|
|
81
|
+
be given in GPa as well.
|
|
82
|
+
"""
|
|
83
|
+
super().__init__(M, mapping=mapping, **kwargs)
|
|
84
|
+
if check_positive_definite:
|
|
85
|
+
_check_definite_positive(self._matrix)
|
|
86
|
+
self.symmetry = symmetry
|
|
87
|
+
self.phase_name = phase_name
|
|
88
|
+
|
|
89
|
+
def __mul__(self, other):
|
|
90
|
+
if isinstance(other, StrainTensor):
|
|
91
|
+
new_tensor = self.ddot(other)
|
|
92
|
+
return StressTensor(new_tensor.matrix)
|
|
93
|
+
elif isinstance(other, StressTensor):
|
|
94
|
+
raise ValueError('You cannot multiply a stiffness tensor with a Stress tensor.')
|
|
95
|
+
else:
|
|
96
|
+
return super().__mul__(other)
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
string = super().__repr__()
|
|
100
|
+
if self.phase_name is not None:
|
|
101
|
+
string += '\nPhase: {}'.format(self.phase_name)
|
|
102
|
+
string += '\nSymmetry: {}'.format(self.symmetry)
|
|
103
|
+
return string
|
|
104
|
+
|
|
105
|
+
def inv(self):
|
|
106
|
+
"""
|
|
107
|
+
Compute the reciprocal compliance tensor
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
ComplianceTensor
|
|
112
|
+
Reciprocal tensor
|
|
113
|
+
"""
|
|
114
|
+
C = np.linalg.inv(self._matrix)
|
|
115
|
+
return ComplianceTensor(C, symmetry=self.symmetry, phase_name=self.phase_name)
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def from_txt_file(cls, filename):
|
|
119
|
+
"""
|
|
120
|
+
Load the tensor from a text file.
|
|
121
|
+
|
|
122
|
+
The two first lines can have data about phase name and symmetry, but this is not mandatory.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
filename : str
|
|
127
|
+
Filename to load the tensor from.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
SymmetricFourthOrderTensor
|
|
132
|
+
The reconstructed tensor read from the file.
|
|
133
|
+
|
|
134
|
+
See Also
|
|
135
|
+
--------
|
|
136
|
+
save_to_txt : create a tensor from text file
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
with open(filename, 'r') as f:
|
|
140
|
+
lines = f.readlines()
|
|
141
|
+
|
|
142
|
+
# Initialize defaults
|
|
143
|
+
phase_name = None
|
|
144
|
+
symmetry = 'Triclinic'
|
|
145
|
+
matrix_start_index = 0
|
|
146
|
+
|
|
147
|
+
# Parse phase name if available
|
|
148
|
+
if lines and lines[0].startswith("Phase Name:"):
|
|
149
|
+
phase_name = lines[0].split(": ", 1)[1].strip()
|
|
150
|
+
matrix_start_index += 1
|
|
151
|
+
|
|
152
|
+
# Parse symmetry if available
|
|
153
|
+
if len(lines) > matrix_start_index and lines[matrix_start_index].startswith("Symmetry:"):
|
|
154
|
+
symmetry = lines[matrix_start_index].split(": ", 1)[1].strip()
|
|
155
|
+
matrix_start_index += 1
|
|
156
|
+
|
|
157
|
+
# Parse matrix
|
|
158
|
+
matrix = np.loadtxt(lines[matrix_start_index:])
|
|
159
|
+
|
|
160
|
+
# Return the reconstructed object
|
|
161
|
+
return cls(matrix, phase_name=phase_name, symmetry=symmetry)
|
|
162
|
+
|
|
163
|
+
def save_to_txt(self, filename, matrix_only=False):
|
|
164
|
+
"""
|
|
165
|
+
Save the tensor to a text file.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
filename : str
|
|
170
|
+
Filename to save the tensor to.
|
|
171
|
+
matrix_only : bool, False
|
|
172
|
+
If true, only the components of tje stiffness tensor is saved (no data about phase nor symmetry)
|
|
173
|
+
|
|
174
|
+
See Also
|
|
175
|
+
--------
|
|
176
|
+
from_txt_file : create a tensor from text file
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
with open(filename, 'w') as f:
|
|
180
|
+
if not matrix_only:
|
|
181
|
+
if self.phase_name is not None:
|
|
182
|
+
f.write(f"Phase Name: {self.phase_name}\n")
|
|
183
|
+
f.write(f"Symmetry: {self.symmetry}\n")
|
|
184
|
+
for row in self._matrix:
|
|
185
|
+
f.write(" " + " ".join(f"{value:8.2f}" for value in row) + "\n")
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def _matrixFromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', prefix=None, **kwargs):
|
|
189
|
+
if prefix is None:
|
|
190
|
+
prefix = cls.component_prefix
|
|
191
|
+
values = _parse_tensor_components(prefix, **kwargs)
|
|
192
|
+
C = np.zeros((6, 6))
|
|
193
|
+
symmetry = symmetry.capitalize()
|
|
194
|
+
if ((symmetry == 'tetragonal') or (symmetry == 'trigonal')) and (point_group is None):
|
|
195
|
+
raise ValueError('For tetragonal and trigonal symmetries, the point group is mandatory.')
|
|
196
|
+
tetra_1 = ['4', '-4', '4/m']
|
|
197
|
+
tetra_2 = ['4mm', '-42m', '422', '4/mmm']
|
|
198
|
+
trigo_1 = ['3', '-3']
|
|
199
|
+
trigo_2 = ['32', '-3m', '3m']
|
|
200
|
+
if point_group is not None:
|
|
201
|
+
if (point_group in tetra_1) or (point_group in tetra_2):
|
|
202
|
+
symmetry = 'Tetragonal'
|
|
203
|
+
elif (point_group in trigo_1) or (point_group in trigo_2):
|
|
204
|
+
symmetry = 'Trigonal'
|
|
205
|
+
symmetry_description = SYMMETRIES[symmetry]
|
|
206
|
+
if symmetry == 'Tetragonal':
|
|
207
|
+
if point_group in tetra_1:
|
|
208
|
+
symmetry_description = symmetry_description[', '.join(tetra_1)]
|
|
209
|
+
else:
|
|
210
|
+
symmetry_description = symmetry_description[', '.join(tetra_2)]
|
|
211
|
+
elif symmetry == 'Trigonal':
|
|
212
|
+
if point_group in trigo_1:
|
|
213
|
+
symmetry_description = symmetry_description[', '.join(trigo_1)]
|
|
214
|
+
else:
|
|
215
|
+
symmetry_description = symmetry_description[', '.join(trigo_2)]
|
|
216
|
+
elif symmetry == 'Monoclinic':
|
|
217
|
+
symmetry_description = symmetry_description["Diad || " + diad]
|
|
218
|
+
for required_field in symmetry_description.required:
|
|
219
|
+
C[required_field] = values[_indices2str(required_field)]
|
|
220
|
+
|
|
221
|
+
# Now apply relationships between components
|
|
222
|
+
for equality in symmetry_description.equal:
|
|
223
|
+
for index in equality[1]:
|
|
224
|
+
C[index] = C[equality[0]]
|
|
225
|
+
for opposite in symmetry_description.opposite:
|
|
226
|
+
for index in opposite[1]:
|
|
227
|
+
C[index] = -C[opposite[0]]
|
|
228
|
+
C11_C12 = symmetry_description.C11_C12
|
|
229
|
+
if C11_C12:
|
|
230
|
+
for index in C11_C12:
|
|
231
|
+
C[index] = (C[0, 0] - C[0, 1]) * cls.C11_C12_factor
|
|
232
|
+
|
|
233
|
+
if symmetry == 'Trigonal':
|
|
234
|
+
C[3, 5] = cls.C46_C56_factor * C[3, 5]
|
|
235
|
+
C[4, 5] = cls.C46_C56_factor * C[4, 5]
|
|
236
|
+
|
|
237
|
+
return C + np.tril(C.T, -1)
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def fromCrystalSymmetry(cls, symmetry='Triclinic', point_group=None, diad='y', phase_name=None, prefix=None,
|
|
241
|
+
**kwargs):
|
|
242
|
+
"""
|
|
243
|
+
Create a fourth-order tensor from limited number of components, taking advantage of crystallographic symmetries
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
symmetry : str, default Triclinic
|
|
248
|
+
Name of the crystallographic symmetry
|
|
249
|
+
point_group : str
|
|
250
|
+
Point group of the considered crystal. Only used (and mandatory) for tetragonal and trigonal symmetries.
|
|
251
|
+
diad : str {'x', 'y'}, default 'x'
|
|
252
|
+
Alignment convention. Sets whether x||a or y||b. Only used for monoclinic symmetry.
|
|
253
|
+
phase_name : str, default None
|
|
254
|
+
Name to use when printing the tensor
|
|
255
|
+
prefix : str, default None
|
|
256
|
+
Define the prefix to use when providing the components. By default, it is 'C' for stiffness tensors, 'S' for
|
|
257
|
+
compliance.
|
|
258
|
+
kwargs
|
|
259
|
+
Keywords describing all the necessary components, depending on the crystal's symmetry and the type of tensor.
|
|
260
|
+
For Stiffness, they should be named as 'Cij' (e.g. C11=..., C12=...).
|
|
261
|
+
For Comliance, they should be named as 'Sij' (e.g. S11=..., S12=...).
|
|
262
|
+
See examples below. The behaviour can be overriten with the prefix option (see above)
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
FourthOrderTensor
|
|
267
|
+
|
|
268
|
+
See Also
|
|
269
|
+
--------
|
|
270
|
+
isotropic : creates an isotropic stiffness tensor from two paremeters (e.g. E and v).
|
|
271
|
+
|
|
272
|
+
Notes
|
|
273
|
+
-----
|
|
274
|
+
The relationships between the tensor's components depend on the crystallogrpahic symmetry [1]_.
|
|
275
|
+
|
|
276
|
+
References
|
|
277
|
+
----------
|
|
278
|
+
.. [1] Nye, J. F. Physical Properties of Crystals. London: Oxford University Press, 1959.
|
|
279
|
+
|
|
280
|
+
Examples
|
|
281
|
+
--------
|
|
282
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor\n
|
|
283
|
+
>>> StiffnessTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
|
|
284
|
+
... C11=231, C12=127, C13=104,
|
|
285
|
+
... C22=240, C23=131, C33=175,
|
|
286
|
+
... C44=81, C55=11, C66=85,
|
|
287
|
+
... C15=-18, C25=1, C35=-3, C46=3)
|
|
288
|
+
Stiffness tensor (in Voigt mapping):
|
|
289
|
+
[[231. 127. 104. 0. -18. 0.]
|
|
290
|
+
[127. 240. 131. 0. 1. 0.]
|
|
291
|
+
[104. 131. 175. 0. -3. 0.]
|
|
292
|
+
[ 0. 0. 0. 81. 0. 3.]
|
|
293
|
+
[-18. 1. -3. 0. 11. 0.]
|
|
294
|
+
[ 0. 0. 0. 3. 0. 85.]]
|
|
295
|
+
Phase: TiNi
|
|
296
|
+
Symmetry: monoclinic
|
|
297
|
+
|
|
298
|
+
>>> from Elasticipy.tensors.elasticity import ComplianceTensor\n
|
|
299
|
+
>>> ComplianceTensor.fromCrystalSymmetry(symmetry='monoclinic', diad='y', phase_name='TiNi',
|
|
300
|
+
... S11=8, S12=-3, S13=-2,
|
|
301
|
+
... S22=8, S23=-5, S33=10,
|
|
302
|
+
... S44=12, S55=116, S66=12,
|
|
303
|
+
... S15=14, S25=-8, S35=0, S46=0)
|
|
304
|
+
Compliance tensor (in Voigt mapping):
|
|
305
|
+
[[ 8. -3. -2. 0. 14. 0.]
|
|
306
|
+
[ -3. 8. -5. 0. -8. 0.]
|
|
307
|
+
[ -2. -5. 10. 0. 0. 0.]
|
|
308
|
+
[ 0. 0. 0. 12. 0. 0.]
|
|
309
|
+
[ 14. -8. 0. 0. 116. 0.]
|
|
310
|
+
[ 0. 0. 0. 0. 0. 12.]]
|
|
311
|
+
Phase: TiNi
|
|
312
|
+
Symmetry: monoclinic
|
|
313
|
+
"""
|
|
314
|
+
matrix = cls._matrixFromCrystalSymmetry(point_group=point_group, diad=diad, symmetry=symmetry, prefix=prefix,
|
|
315
|
+
**kwargs)
|
|
316
|
+
return cls(matrix, phase_name=phase_name, symmetry=symmetry)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def hexagonal(cls, *, C11=0., C12=0., C13=0., C33=0., C44=0., phase_name=None):
|
|
321
|
+
"""
|
|
322
|
+
Create a fourth-order tensor from hexagonal symmetry.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
C11, C12 , C13, C33, C44 : float
|
|
327
|
+
Components of the tensor, using the Voigt notation
|
|
328
|
+
phase_name : str, optional
|
|
329
|
+
Phase name to display
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
FourthOrderTensor
|
|
333
|
+
|
|
334
|
+
See Also
|
|
335
|
+
--------
|
|
336
|
+
transverse_isotropic : creates a transverse-isotropic tensor from engineering parameters
|
|
337
|
+
cubic : create a tensor from cubic symmetry
|
|
338
|
+
tetragonal : create a tensor from tetragonal symmetry
|
|
339
|
+
"""
|
|
340
|
+
return cls.fromCrystalSymmetry(symmetry='hexagonal', C11=C11, C12=C12, C13=C13, C33=C33, C44=C44,
|
|
341
|
+
phase_name=phase_name, prefix='C')
|
|
342
|
+
|
|
343
|
+
@classmethod
|
|
344
|
+
def trigonal(cls, *, C11=0., C12=0., C13=0., C14=0., C33=0., C44=0., C15=0., phase_name=None):
|
|
345
|
+
"""
|
|
346
|
+
Create a fourth-order tensor from trigonal symmetry.
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
C11, C12, C13, C14, C33, C44 : float
|
|
351
|
+
Components of the tensor, using the Voigt notation
|
|
352
|
+
C15 : float, optional
|
|
353
|
+
C15 component of the tensor, only used for point groups 3 and -3.
|
|
354
|
+
phase_name : str, optional
|
|
355
|
+
Phase name to display
|
|
356
|
+
Returns
|
|
357
|
+
-------
|
|
358
|
+
FourthOrderTensor
|
|
359
|
+
|
|
360
|
+
See Also
|
|
361
|
+
--------
|
|
362
|
+
tetragonal : create a tensor from tetragonal symmetry
|
|
363
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
364
|
+
"""
|
|
365
|
+
return cls.fromCrystalSymmetry(point_group='3', C11=C11, C12=C12, C13=C13, C14=C14, C15=C15,
|
|
366
|
+
C33=C33, C44=C44, phase_name=phase_name, prefix='C')
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def tetragonal(cls, *, C11=0., C12=0., C13=0., C33=0., C44=0., C16=0., C66=0., phase_name=None):
|
|
370
|
+
"""
|
|
371
|
+
Create a fourth-order tensor from tetragonal symmetry.
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
C11, C12, C13, C33, C44, C66 : float
|
|
376
|
+
Components of the tensor, using the Voigt notation
|
|
377
|
+
C16 : float, optional
|
|
378
|
+
C16 component in Voigt notation (for point groups 4, -4 and 4/m only)
|
|
379
|
+
phase_name : str, optional
|
|
380
|
+
Phase name to display
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
FourthOrderTensor
|
|
385
|
+
|
|
386
|
+
See Also
|
|
387
|
+
--------
|
|
388
|
+
trigonal : create a tensor from trigonal symmetry
|
|
389
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
390
|
+
"""
|
|
391
|
+
return cls.fromCrystalSymmetry(point_group='4', C11=C11, C12=C12, C13=C13, C16=C16,
|
|
392
|
+
C33=C33, C44=C44, C66=C66, phase_name=phase_name, prefix='C')
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def cubic(cls, *, C11=0., C12=0., C44=0., phase_name=None):
|
|
396
|
+
"""
|
|
397
|
+
Create a fourth-order tensor from cubic symmetry.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
C11 , C12, C44 : float
|
|
402
|
+
phase_name : str, optional
|
|
403
|
+
Phase name to display
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
StiffnessTensor
|
|
408
|
+
|
|
409
|
+
See Also
|
|
410
|
+
--------
|
|
411
|
+
hexagonal : create a tensor from hexagonal symmetry
|
|
412
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
413
|
+
"""
|
|
414
|
+
return cls.fromCrystalSymmetry(symmetry='cubic', C11=C11, C12=C12, C44=C44, phase_name=phase_name, prefix='C')
|
|
415
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
def orthorhombic(cls, *, C11=0., C12=0., C13=0., C22=0., C23=0., C33=0., C44=0., C55=0., C66=0., phase_name=None):
|
|
418
|
+
"""
|
|
419
|
+
Create a fourth-order tensor from orthorhombic symmetry.
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
C11, C12, C13, C22, C23, C33, C44, C55, C66 : float
|
|
424
|
+
Components of the tensor, using the Voigt notation
|
|
425
|
+
phase_name : str, optional
|
|
426
|
+
Phase name to display
|
|
427
|
+
|
|
428
|
+
Returns
|
|
429
|
+
-------
|
|
430
|
+
FourthOrderTensor
|
|
431
|
+
|
|
432
|
+
See Also
|
|
433
|
+
--------
|
|
434
|
+
monoclinic : create a tensor from monoclinic symmetry
|
|
435
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
436
|
+
"""
|
|
437
|
+
return cls.fromCrystalSymmetry(symmetry='orthorhombic',
|
|
438
|
+
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55, C66=C66,
|
|
439
|
+
phase_name=phase_name, prefix='C')
|
|
440
|
+
|
|
441
|
+
@classmethod
|
|
442
|
+
def monoclinic(cls, *, C11=0., C12=0., C13=0., C22=0., C23=0., C33=0., C44=0., C55=0., C66=0.,
|
|
443
|
+
C15=None, C25=None, C35=None, C46=None,
|
|
444
|
+
C16=None, C26=None, C36=None, C45=None,
|
|
445
|
+
phase_name=None):
|
|
446
|
+
"""
|
|
447
|
+
Create a fourth-order tensor from monoclinic symmetry. It automatically detects whether the components are given
|
|
448
|
+
according to the Y or Z diad, depending on the input arguments.
|
|
449
|
+
|
|
450
|
+
For Diad || y, C15, C25, C35 and C46 must be provided.
|
|
451
|
+
For Diad || z, C16, C26, C36 and C45 must be provided.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
C11, C12 , C13, C22, C23, C33, C44, C55, C66 : float
|
|
456
|
+
Components of the tensor, using the Voigt notation
|
|
457
|
+
C15 : float, optional
|
|
458
|
+
C15 component of the tensor (if Diad || y)
|
|
459
|
+
C25 : float, optional
|
|
460
|
+
C25 component of the tensor (if Diad || y)
|
|
461
|
+
C35 : float, optional
|
|
462
|
+
C35 component of the tensor (if Diad || y)
|
|
463
|
+
C46 : float, optional
|
|
464
|
+
C46 component of the tensor (if Diad || y)
|
|
465
|
+
C16 : float, optional
|
|
466
|
+
C16 component of the tensor (if Diad || z)
|
|
467
|
+
C26 : float, optional
|
|
468
|
+
C26 component of the tensor (if Diad || z)
|
|
469
|
+
C36 : float, optional
|
|
470
|
+
C36 component of the tensor (if Diad || z)
|
|
471
|
+
C45 : float, optional
|
|
472
|
+
C45 component of the tensor (if Diad || z)
|
|
473
|
+
phase_name : str, optional
|
|
474
|
+
Name to display
|
|
475
|
+
|
|
476
|
+
Returns
|
|
477
|
+
-------
|
|
478
|
+
FourthOrderTensor
|
|
479
|
+
|
|
480
|
+
See Also
|
|
481
|
+
--------
|
|
482
|
+
triclinic : create a tensor from triclinic symmetry
|
|
483
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
484
|
+
"""
|
|
485
|
+
diad_y = not (None in (C15, C25, C35, C46))
|
|
486
|
+
diad_z = not (None in (C16, C26, C36, C45))
|
|
487
|
+
if diad_y and diad_z:
|
|
488
|
+
raise KeyError('Ambiguous diad. Provide either C15, C25, C35 and C46; or C16, C26, C36 and C45')
|
|
489
|
+
elif diad_y:
|
|
490
|
+
return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='y',
|
|
491
|
+
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
|
|
492
|
+
C66=C66,
|
|
493
|
+
C15=C15, C25=C25, C35=C35, C46=C46, phase_name=phase_name, prefix='C')
|
|
494
|
+
elif diad_z:
|
|
495
|
+
return cls.fromCrystalSymmetry(symmetry='monoclinic', diad='z',
|
|
496
|
+
C11=C11, C12=C12, C13=C13, C22=C22, C23=C23, C33=C33, C44=C44, C55=C55,
|
|
497
|
+
C66=C66,
|
|
498
|
+
C16=C16, C26=C26, C36=C36, C45=C45, phase_name=phase_name, prefix='C')
|
|
499
|
+
else:
|
|
500
|
+
raise KeyError('For monoclinic symmetry, one should provide either C15, C25, C35 and C46, '
|
|
501
|
+
'or C16, C26, C36 and C45.')
|
|
502
|
+
|
|
503
|
+
@classmethod
|
|
504
|
+
def triclinic(cls, C11=0., C12=0., C13=0., C14=0., C15=0., C16=0.,
|
|
505
|
+
C22=0., C23=0., C24=0., C25=0., C26=0.,
|
|
506
|
+
C33=0., C34=0., C35=0., C36=0.,
|
|
507
|
+
C44=0., C45=0., C46=0.,
|
|
508
|
+
C55=0., C56=0.,
|
|
509
|
+
C66=0., phase_name=None):
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
C11 , C12 , C13 , C14 , C15 , C16 , C22 , C23 , C24 , C25 , C26 , C33 , C34 , C35 , C36 , C44 , C45 , C46 , C55 , C56 , C66 : float
|
|
515
|
+
Components of the tensor
|
|
516
|
+
phase_name : str, optional
|
|
517
|
+
Name to display
|
|
518
|
+
|
|
519
|
+
Returns
|
|
520
|
+
-------
|
|
521
|
+
FourthOrderTensor
|
|
522
|
+
|
|
523
|
+
See Also
|
|
524
|
+
--------
|
|
525
|
+
monoclinic : create a tensor from monoclinic symmetry
|
|
526
|
+
orthorhombic : create a tensor from orthorhombic symmetry
|
|
527
|
+
"""
|
|
528
|
+
matrix = np.array([[C11, C12, C13, C14, C15, C16],
|
|
529
|
+
[C12, C22, C23, C24, C25, C26],
|
|
530
|
+
[C13, C23, C33, C34, C35, C36],
|
|
531
|
+
[C14, C24, C34, C44, C45, C46],
|
|
532
|
+
[C15, C25, C35, C45, C55, C56],
|
|
533
|
+
[C16, C26, C36, C46, C56, C66]])
|
|
534
|
+
return cls(matrix, phase_name=phase_name)
|
|
535
|
+
|
|
536
|
+
def _single_tensor_only(self, fun_name=''):
|
|
537
|
+
if self.ndim:
|
|
538
|
+
err_msg = fun_name + ' is not suitable for tensor array. Consider subscripting (e.g. C[0].{}).'.format(fun_name)
|
|
539
|
+
raise ValueError(err_msg)
|
|
540
|
+
|
|
541
|
+
@property
|
|
542
|
+
def Young_modulus(self):
|
|
543
|
+
"""
|
|
544
|
+
Directional Young's modulus
|
|
545
|
+
|
|
546
|
+
Returns
|
|
547
|
+
-------
|
|
548
|
+
SphericalFunction
|
|
549
|
+
Young's modulus
|
|
550
|
+
"""
|
|
551
|
+
self._single_tensor_only('Young_modulus')
|
|
552
|
+
if isinstance(self, ComplianceTensor):
|
|
553
|
+
S = self
|
|
554
|
+
else:
|
|
555
|
+
S = self.inv()
|
|
556
|
+
def compute_young_modulus(u):
|
|
557
|
+
a = np.einsum('ijkl,...i,...j,...k,...l->...', S.full_tensor(), u, u, u, u)
|
|
558
|
+
return 1 / a
|
|
559
|
+
|
|
560
|
+
return SphericalFunction(compute_young_modulus)
|
|
561
|
+
|
|
562
|
+
@property
|
|
563
|
+
def shear_modulus(self):
|
|
564
|
+
"""
|
|
565
|
+
Directional shear modulus
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
-------
|
|
569
|
+
HyperSphericalFunction
|
|
570
|
+
Shear modulus
|
|
571
|
+
"""
|
|
572
|
+
self._single_tensor_only('shear_modulus')
|
|
573
|
+
if isinstance(self, ComplianceTensor):
|
|
574
|
+
S = self
|
|
575
|
+
else:
|
|
576
|
+
S = self.inv()
|
|
577
|
+
def compute_shear_modulus(u, v):
|
|
578
|
+
G = 0.25/np.einsum('ijkl,...i,...j,...k,...l->...',S.full_tensor(),u,v,u,v)
|
|
579
|
+
return G
|
|
580
|
+
|
|
581
|
+
return HyperSphericalFunction(compute_shear_modulus)
|
|
582
|
+
|
|
583
|
+
@property
|
|
584
|
+
def Poisson_ratio(self):
|
|
585
|
+
"""
|
|
586
|
+
Directional Poisson's ratio
|
|
587
|
+
|
|
588
|
+
Returns
|
|
589
|
+
-------
|
|
590
|
+
HyperSphericalFunction
|
|
591
|
+
Poisson's ratio
|
|
592
|
+
|
|
593
|
+
Notes
|
|
594
|
+
-----
|
|
595
|
+
If the material undergoes tensile strain :math:`\\varepsilon_{ii}` along the i-th direction, the Poisson ratios
|
|
596
|
+
are defined as:
|
|
597
|
+
|
|
598
|
+
.. math::
|
|
599
|
+
|
|
600
|
+
\\nu_{ij}=-\\frac{\\partial \\varepsilon_{jj}}{\\partial \\varepsilon_{ii}}
|
|
601
|
+
|
|
602
|
+
where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction.
|
|
603
|
+
"""
|
|
604
|
+
self._single_tensor_only('Poisson_ratio')
|
|
605
|
+
if isinstance(self, ComplianceTensor):
|
|
606
|
+
Sfull = self.full_tensor()
|
|
607
|
+
else:
|
|
608
|
+
Sfull = self.inv().full_tensor()
|
|
609
|
+
def compute_PoissonRatio(u, v):
|
|
610
|
+
numer = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,v,v,u,u)
|
|
611
|
+
denom = np.einsum('ijkl,...i,...j,...k,...l->...',Sfull,u,u,u,u)
|
|
612
|
+
return -numer / denom
|
|
613
|
+
|
|
614
|
+
return HyperSphericalFunction(compute_PoissonRatio)
|
|
615
|
+
|
|
616
|
+
@property
|
|
617
|
+
def linear_compressibility(self):
|
|
618
|
+
"""
|
|
619
|
+
Compute the directional linear compressibility.
|
|
620
|
+
|
|
621
|
+
Returns
|
|
622
|
+
-------
|
|
623
|
+
SphericalFunction
|
|
624
|
+
Directional linear compressibility
|
|
625
|
+
|
|
626
|
+
See Also
|
|
627
|
+
--------
|
|
628
|
+
bulk_modulus : bulk modulus of the material
|
|
629
|
+
"""
|
|
630
|
+
self._single_tensor_only('linear_compressibility')
|
|
631
|
+
if isinstance(self, ComplianceTensor):
|
|
632
|
+
S = self
|
|
633
|
+
else:
|
|
634
|
+
S = self.inv()
|
|
635
|
+
def compute_linear_compressibility(u):
|
|
636
|
+
return np.einsum('ijkk,...i,...j->...',S.full_tensor(),u,u)
|
|
637
|
+
|
|
638
|
+
return SphericalFunction(compute_linear_compressibility)
|
|
639
|
+
|
|
640
|
+
@property
|
|
641
|
+
def bulk_modulus(self):
|
|
642
|
+
"""
|
|
643
|
+
Compute the bulk modulus of the material
|
|
644
|
+
|
|
645
|
+
Returns
|
|
646
|
+
-------
|
|
647
|
+
float or numpy.ndarray
|
|
648
|
+
Bulk modulus
|
|
649
|
+
|
|
650
|
+
See Also
|
|
651
|
+
--------
|
|
652
|
+
linear_compressibility : directional linear compressibility
|
|
653
|
+
"""
|
|
654
|
+
return self.inv().bulk_modulus
|
|
655
|
+
|
|
656
|
+
def Voigt_average(self, axis=None):
|
|
657
|
+
"""
|
|
658
|
+
Compute the Voigt average of the stiffness tensor.
|
|
659
|
+
|
|
660
|
+
If the tensor is a tensor array, all its values are considered. Otherwise (i.e. if single), the corresponding
|
|
661
|
+
isotropic tensor is returned.
|
|
662
|
+
|
|
663
|
+
Parameters
|
|
664
|
+
----------
|
|
665
|
+
axis : int, optional
|
|
666
|
+
If provided, the average is computed along this axis. Otherwise, the mean is computed on the flattened array.
|
|
667
|
+
|
|
668
|
+
Returns
|
|
669
|
+
-------
|
|
670
|
+
StiffnessTensor
|
|
671
|
+
Voigt average of stiffness tensor
|
|
672
|
+
|
|
673
|
+
See Also
|
|
674
|
+
--------
|
|
675
|
+
Reuss_average : compute the Reuss average
|
|
676
|
+
Hill_average : compute the Voigt-Reuss-Hill average
|
|
677
|
+
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
678
|
+
"""
|
|
679
|
+
if self.ndim:
|
|
680
|
+
return self.mean(axis=axis)
|
|
681
|
+
else:
|
|
682
|
+
c = self._matrix
|
|
683
|
+
A = c[0, 0] + c[1, 1] + c[2, 2]
|
|
684
|
+
B = c[0, 1] + c[0, 2] + c[1, 2]
|
|
685
|
+
C = c[3, 3] + c[4, 4] + c[5, 5]
|
|
686
|
+
C11 = 1 / 5 * A + 2 / 15 * B + 4 / 15 * C
|
|
687
|
+
C12 = 1 / 15 * A + 4 / 15 * B - 2 / 15 * C
|
|
688
|
+
C44 = (A - B) / 15 + C / 5
|
|
689
|
+
mat = _isotropic_matrix(C11, C12, C44)
|
|
690
|
+
return StiffnessTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
691
|
+
|
|
692
|
+
def Reuss_average(self, axis=None):
|
|
693
|
+
"""
|
|
694
|
+
Compute the Reuss average of the stiffness tensor. If the tensor contains no orientation, we assume isotropic
|
|
695
|
+
behaviour. Otherwise, the mean is computed over all orientations.
|
|
696
|
+
|
|
697
|
+
Parameters
|
|
698
|
+
----------
|
|
699
|
+
axis : int, optional
|
|
700
|
+
If provided, axis to compute the average along with. If none, the average is computed on the flattened array
|
|
701
|
+
|
|
702
|
+
Returns
|
|
703
|
+
-------
|
|
704
|
+
StiffnessTensor
|
|
705
|
+
Reuss average of stiffness tensor
|
|
706
|
+
|
|
707
|
+
See Also
|
|
708
|
+
--------
|
|
709
|
+
Voigt_average : compute the Voigt average
|
|
710
|
+
Hill_average : compute the Voigt-Reuss-Hill average
|
|
711
|
+
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
712
|
+
"""
|
|
713
|
+
return self.inv().Reuss_average(axis=axis).inv()
|
|
714
|
+
|
|
715
|
+
def Hill_average(self, axis=None):
|
|
716
|
+
"""
|
|
717
|
+
Compute the (Voigt-Reuss-)Hill average of the stiffness tensor. If the tensor contains no orientation, we assume
|
|
718
|
+
isotropic behaviour. Otherwise, the mean is computed over all orientations.
|
|
719
|
+
|
|
720
|
+
Parameters
|
|
721
|
+
----------
|
|
722
|
+
axis : int, optional
|
|
723
|
+
If provided, axis to compute the average along with. If none, the average is computed on the flattened array
|
|
724
|
+
|
|
725
|
+
Returns
|
|
726
|
+
-------
|
|
727
|
+
StiffnessTensor
|
|
728
|
+
Voigt-Reuss-Hill average of tensor
|
|
729
|
+
|
|
730
|
+
See Also
|
|
731
|
+
--------
|
|
732
|
+
Voigt_average : compute the Voigt average
|
|
733
|
+
Reuss_average : compute the Reuss average
|
|
734
|
+
average : generic function for calling either the Voigt, Reuss or Hill average
|
|
735
|
+
"""
|
|
736
|
+
Reuss = self.Reuss_average(axis=axis)
|
|
737
|
+
Voigt = self.Voigt_average(axis=axis)
|
|
738
|
+
return (Reuss + Voigt) * 0.5
|
|
739
|
+
|
|
740
|
+
def average(self, method, axis=None):
|
|
741
|
+
"""
|
|
742
|
+
Compute either the Voigt, Reuss, or Hill average of the stiffness tensor.
|
|
743
|
+
|
|
744
|
+
This function is just a shortcut for Voigt_average(), Reuss_average(), or Hill_average() and Hill_average().
|
|
745
|
+
|
|
746
|
+
Parameters
|
|
747
|
+
----------
|
|
748
|
+
axis : int, optional
|
|
749
|
+
If provided, axis to compute the average along with. If none, the average is computed on the flattened array
|
|
750
|
+
method : str {'Voigt', 'Reuss', 'Hill'}
|
|
751
|
+
Method to use to compute the average.
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
StiffnessTensor
|
|
756
|
+
|
|
757
|
+
See Also
|
|
758
|
+
--------
|
|
759
|
+
Voigt_average : compute the Voigt average
|
|
760
|
+
Reuss_average : compute the Reuss average
|
|
761
|
+
Hill_average : compute the Voigt-Reuss-Hill average
|
|
762
|
+
"""
|
|
763
|
+
method = method.capitalize()
|
|
764
|
+
if method in ('Voigt', 'Reuss', 'Hill'):
|
|
765
|
+
fun = getattr(self, method + '_average')
|
|
766
|
+
return fun(axis=axis)
|
|
767
|
+
else:
|
|
768
|
+
raise NotImplementedError('Only Voigt, Reus, and Hill are implemented.')
|
|
769
|
+
|
|
770
|
+
@classmethod
|
|
771
|
+
def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
|
|
772
|
+
"""
|
|
773
|
+
Create an isotropic stiffness tensor from two elasticity coefficients, namely: E, nu, lame1, or lame2. Exactly
|
|
774
|
+
two of these coefficients must be provided.
|
|
775
|
+
|
|
776
|
+
Parameters
|
|
777
|
+
----------
|
|
778
|
+
E : float, None
|
|
779
|
+
Young modulus
|
|
780
|
+
nu : float, None
|
|
781
|
+
Poisson ratio
|
|
782
|
+
lame1 : float, None
|
|
783
|
+
First Lamé coefficient
|
|
784
|
+
lame2 : float, None
|
|
785
|
+
Second Lamé coefficient
|
|
786
|
+
phase_name : str, None
|
|
787
|
+
Name to print
|
|
788
|
+
|
|
789
|
+
Returns
|
|
790
|
+
-------
|
|
791
|
+
Corresponding isotropic stiffness tensor
|
|
792
|
+
|
|
793
|
+
See Also
|
|
794
|
+
--------
|
|
795
|
+
transverse_isotropic : create a transverse-isotropic tensor
|
|
796
|
+
|
|
797
|
+
Notes
|
|
798
|
+
-----
|
|
799
|
+
The units you use when passing the elastic moduli must be consistent with that of the stress tensor. For
|
|
800
|
+
instance, if you expect to work in MPa, the Young's modulus and the Lamé's coefficient must be given in MPa as
|
|
801
|
+
well.
|
|
802
|
+
|
|
803
|
+
Examples
|
|
804
|
+
--------
|
|
805
|
+
On can check that the shear modulus for steel is around 82 GPa:
|
|
806
|
+
|
|
807
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor
|
|
808
|
+
>>> C=StiffnessTensor.isotropic(E=210e3, nu=0.28)
|
|
809
|
+
>>> C.shear_modulus
|
|
810
|
+
Hyperspherical function
|
|
811
|
+
Min=82031.24999999997, Max=82031.25000000006
|
|
812
|
+
"""
|
|
813
|
+
argument_vector = np.array([E, nu, lame1, lame2])
|
|
814
|
+
if np.count_nonzero(argument_vector) != 2:
|
|
815
|
+
raise ValueError("Exactly two values are required among E, nu, lame1 and lame2.")
|
|
816
|
+
if E is not None:
|
|
817
|
+
if nu is not None:
|
|
818
|
+
lame1 = E * nu / ((1 + nu) * (1 - 2 * nu))
|
|
819
|
+
lame2 = E / (1 + nu) / 2
|
|
820
|
+
elif lame1 is not None:
|
|
821
|
+
R = np.sqrt(E ** 2 + 9 * lame1 ** 2 + 2 * E * lame1)
|
|
822
|
+
lame2 = (E - 3 * lame1 + R) / 4
|
|
823
|
+
elif lame2 is not None:
|
|
824
|
+
lame1 = lame2 * (E - 2 * lame2) / (3 * lame2 - E)
|
|
825
|
+
else:
|
|
826
|
+
raise ValueError('Either nu, lame1 or lame2 must be provided.')
|
|
827
|
+
elif nu is not None:
|
|
828
|
+
if lame1 is not None:
|
|
829
|
+
lame2 = lame1 * (1 - 2 * nu) / (2 * nu)
|
|
830
|
+
elif lame2 is not None:
|
|
831
|
+
lame1 = 2 * lame2 * nu / (1 - 2 * nu)
|
|
832
|
+
else:
|
|
833
|
+
raise ValueError('Either lame1 or lame2 must be provided.')
|
|
834
|
+
C11 = lame1 + 2 * lame2
|
|
835
|
+
C12 = lame1
|
|
836
|
+
C44 = lame2
|
|
837
|
+
matrix = _isotropic_matrix(C11, C12, C44)
|
|
838
|
+
return StiffnessTensor(np.array(matrix), symmetry='isotropic', phase_name=phase_name)
|
|
839
|
+
|
|
840
|
+
@classmethod
|
|
841
|
+
def orthotropic(cls, *, Ex, Ey, Ez, Gxy, Gxz, Gyz,
|
|
842
|
+
nu_yx=None, nu_zx=None, nu_zy=None,
|
|
843
|
+
nu_xy=None, nu_xz=None, nu_yz=None, **kwargs):
|
|
844
|
+
"""
|
|
845
|
+
Create a stiffness tensor corresponding to orthotropic symmetry, given the engineering constants.
|
|
846
|
+
|
|
847
|
+
Exactly three Poisson ratios must be provided. See Notes for details.
|
|
848
|
+
|
|
849
|
+
Parameters
|
|
850
|
+
----------
|
|
851
|
+
Ex : float
|
|
852
|
+
Young modulus along the x axis
|
|
853
|
+
Ey : float
|
|
854
|
+
Young modulus along the y axis
|
|
855
|
+
Ez : float
|
|
856
|
+
Young modulus along the z axis
|
|
857
|
+
Gxy : float
|
|
858
|
+
Shear modulus in the x-y plane
|
|
859
|
+
Gxz : float
|
|
860
|
+
Shear modulus in the x-z plane
|
|
861
|
+
Gyz : float
|
|
862
|
+
Shear modulus in the y-z plane
|
|
863
|
+
nu_xy, nu_yx : float, optional
|
|
864
|
+
Poisson ratio along x and y axes. Either nu_xy or nu_yx must be provided, not both.
|
|
865
|
+
nu_xz, nu_zx : float, optional
|
|
866
|
+
Poisson ratio along x and z axes. Either nu_xz or nu_zx must be provided, not both.
|
|
867
|
+
nu_yz, nu_zy : float, optional
|
|
868
|
+
Poisson ratio along y and z axes. Either nu_yz or nu_zy must be provided, not both.
|
|
869
|
+
kwargs : dict, optional
|
|
870
|
+
Keyword arguments to pass to the StiffnessTensor constructor
|
|
871
|
+
|
|
872
|
+
Returns
|
|
873
|
+
-------
|
|
874
|
+
StiffnessTensor
|
|
875
|
+
|
|
876
|
+
See Also
|
|
877
|
+
--------
|
|
878
|
+
transverse_isotropic : create a stiffness tensor for transverse-isotropic symmetry
|
|
879
|
+
|
|
880
|
+
Notes
|
|
881
|
+
-----
|
|
882
|
+
If the material undergoes tensile strain :math:`\\varepsilon_{ii}` along the i-th direction, the Poisson ratios
|
|
883
|
+
are defined as:
|
|
884
|
+
|
|
885
|
+
.. math::
|
|
886
|
+
|
|
887
|
+
\\nu_{ij}=-\\frac{\\partial \\varepsilon_{jj}}{\\partial \\varepsilon_{ii}}
|
|
888
|
+
|
|
889
|
+
where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction. If
|
|
890
|
+
:math:`E_x` and :math:`E_y` are the Young moduli along `x` and `y`, we have:
|
|
891
|
+
|
|
892
|
+
.. math::
|
|
893
|
+
|
|
894
|
+
\\frac{\\nu_{xy}}{E_x} = \\frac{\\nu_{yx}}{E_y}
|
|
895
|
+
|
|
896
|
+
"""
|
|
897
|
+
nu_yx = _switch_poisson_ratios(nu_xy, nu_yx, Ex, Ey,'xy')
|
|
898
|
+
nu_zx = _switch_poisson_ratios(nu_xz, nu_zx, Ex, Ez,'xz')
|
|
899
|
+
nu_zy = _switch_poisson_ratios(nu_yz, nu_zy, Ey, Ez,'yz')
|
|
900
|
+
tri_sup = np.array([[1 / Ex, -nu_yx / Ey, -nu_zx / Ez, 0, 0, 0],
|
|
901
|
+
[0, 1 / Ey, -nu_zy / Ez, 0, 0, 0],
|
|
902
|
+
[0, 0, 1 / Ez, 0, 0, 0],
|
|
903
|
+
[0, 0, 0, 1 / Gyz, 0, 0],
|
|
904
|
+
[0, 0, 0, 0, 1 / Gxz, 0],
|
|
905
|
+
[0, 0, 0, 0, 0, 1 / Gxy]])
|
|
906
|
+
S = tri_sup + np.tril(tri_sup.T, -1)
|
|
907
|
+
return StiffnessTensor(np.linalg.inv(S), symmetry='orthotropic', **kwargs)
|
|
908
|
+
|
|
909
|
+
@classmethod
|
|
910
|
+
def transverse_isotropic(cls, *, Ex, Ez, Gxz, nu_yx=None, nu_xy=None, nu_zx=None, nu_xz=None, **kwargs):
|
|
911
|
+
"""
|
|
912
|
+
Create a stiffness tensor corresponding to the transversely isotropic symmetry with respect to Z axis, given the
|
|
913
|
+
engineering constants.
|
|
914
|
+
|
|
915
|
+
Exactly two Poisson ratios must be provided (nu_xy or nu_yx, and nu_xz or nu_zx). See Notes for details.
|
|
916
|
+
|
|
917
|
+
Parameters
|
|
918
|
+
----------
|
|
919
|
+
Ex : float
|
|
920
|
+
Young modulus along the x axis
|
|
921
|
+
Ez : float
|
|
922
|
+
Young modulus along the y axis
|
|
923
|
+
Gxz : float
|
|
924
|
+
Shear modulus in the x-z plane
|
|
925
|
+
nu_xy, nu_yx : float, optional
|
|
926
|
+
Poisson ratio along x and y. Either nu_xy or nu_yx must be provided, not both.
|
|
927
|
+
nu_xz, nu_zx : float, optional
|
|
928
|
+
Poisson ratio along x and z. Either nu_xz or nu_zx must be provided, not both.
|
|
929
|
+
kwargs : dict
|
|
930
|
+
Keyword arguments to pass to the StiffnessTensor constructor
|
|
931
|
+
|
|
932
|
+
Returns
|
|
933
|
+
-------
|
|
934
|
+
StiffnessTensor
|
|
935
|
+
|
|
936
|
+
See Also
|
|
937
|
+
--------
|
|
938
|
+
orthotropic : create a stiffness tensor for orthotropic symmetry
|
|
939
|
+
|
|
940
|
+
Notes
|
|
941
|
+
-----
|
|
942
|
+
If the material undergoes tensile strain :math:`\\varepsilon_{ii}` along the i-th direction, the Poisson ratios
|
|
943
|
+
are defined as:
|
|
944
|
+
|
|
945
|
+
.. math::
|
|
946
|
+
|
|
947
|
+
\\nu_{ij}=-\\frac{\\partial \\varepsilon_{jj}}{\\partial \\varepsilon_{ii}}
|
|
948
|
+
|
|
949
|
+
where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction. If
|
|
950
|
+
:math:`E_x` and :math:`E_y` are the Young moduli along `x` and `y`, we have:
|
|
951
|
+
|
|
952
|
+
.. math::
|
|
953
|
+
|
|
954
|
+
\\frac{\\nu_{xy}}{E_x} = \\frac{\\nu_{yx}}{E_y}
|
|
955
|
+
"""
|
|
956
|
+
nu_yx = _switch_poisson_ratios(nu_xy, nu_yx, Ex, Ex,'xy')
|
|
957
|
+
Gxy = Ex / (2 * (1 + nu_yx))
|
|
958
|
+
C = StiffnessTensor.orthotropic(Ex=Ex, Ey=Ex, Ez=Ez,
|
|
959
|
+
nu_yx=nu_yx, nu_zx=nu_zx, nu_zy=nu_zx,
|
|
960
|
+
nu_xz=nu_xz, nu_yz=nu_xz,
|
|
961
|
+
Gxy=Gxy, Gxz=Gxz, Gyz=Gxz, **kwargs)
|
|
962
|
+
C.symmetry = 'transverse-isotropic'
|
|
963
|
+
return C
|
|
964
|
+
|
|
965
|
+
def Christoffel_tensor(self, u):
|
|
966
|
+
"""
|
|
967
|
+
Create the Christoffel tensor along a given direction, or set or directions.
|
|
968
|
+
|
|
969
|
+
Parameters
|
|
970
|
+
----------
|
|
971
|
+
u : list or np.ndarray
|
|
972
|
+
3D direction(s) to compute the Christoffel tensor along with
|
|
973
|
+
|
|
974
|
+
Returns
|
|
975
|
+
-------
|
|
976
|
+
Gamma : np.ndarray
|
|
977
|
+
Array of Christoffel tensor(s). if u is a list of directions, Gamma[i] is the Christoffel tensor for
|
|
978
|
+
direction u[i].
|
|
979
|
+
|
|
980
|
+
See Also
|
|
981
|
+
--------
|
|
982
|
+
wave_velocity : computes the p- and s-wave velocities.
|
|
983
|
+
|
|
984
|
+
Notes
|
|
985
|
+
-----
|
|
986
|
+
For a given stiffness tensor **C** and a given unit vector **u**, the Christoffel tensor is defined as [2]_ :
|
|
987
|
+
|
|
988
|
+
.. math:: M_{ij} = C_{iklj}.u_k.u_l
|
|
989
|
+
|
|
990
|
+
"""
|
|
991
|
+
u_vec = np.atleast_2d(u)
|
|
992
|
+
u_vec = (u_vec.T / np.linalg.norm(u_vec, axis=1)).T
|
|
993
|
+
return np.einsum('inmj,pn,pm->pij', self.full_tensor(), u_vec, u_vec)
|
|
994
|
+
|
|
995
|
+
def wave_velocity(self, rho):
|
|
996
|
+
"""
|
|
997
|
+
Compute the wave velocities, given the mass density.
|
|
998
|
+
|
|
999
|
+
Parameters
|
|
1000
|
+
----------
|
|
1001
|
+
rho : float
|
|
1002
|
+
mass density. Its unit must be consistent with that of the stiffness tensor. See notes for hints.
|
|
1003
|
+
|
|
1004
|
+
See Also
|
|
1005
|
+
--------
|
|
1006
|
+
Christoffel_tensor : Computes the Christoffel tensor along a given direction
|
|
1007
|
+
|
|
1008
|
+
Returns
|
|
1009
|
+
-------
|
|
1010
|
+
c_p : SphericalFunction
|
|
1011
|
+
Velocity of the primary (compressive) wave
|
|
1012
|
+
c_s1 : SphericalFunction
|
|
1013
|
+
Velocity of the fast secondary (shear) wave
|
|
1014
|
+
c_s2 : SphericalFunction
|
|
1015
|
+
Velocity of the slow secondary (shear) wave
|
|
1016
|
+
|
|
1017
|
+
Notes
|
|
1018
|
+
-----
|
|
1019
|
+
The estimation of the wave velocities is made by finding the eigenvalues of the Christoffel tensor [2]_.
|
|
1020
|
+
|
|
1021
|
+
One should double-check the units. The table below provides hints about the unit you get, depending on the units
|
|
1022
|
+
you use for stiffness and the mass density:
|
|
1023
|
+
|
|
1024
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1025
|
+
| Stiffness | Mass density | Velocities | Notes |
|
|
1026
|
+
+=================+==============+============+=======================+
|
|
1027
|
+
| Pa (N/m²) | kg/m³ | m/s | SI units |
|
|
1028
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1029
|
+
| GPa (10⁹ Pa) | kg/dm³ | km/s | Conversion factor |
|
|
1030
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1031
|
+
| GPa (10³ N/mm²) | kg/mm³ | m/s | Consistent units |
|
|
1032
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1033
|
+
| MPa (10⁶ Pa) | kg/m³ | km/s | Conversion factor |
|
|
1034
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1035
|
+
| MPa (10³ N/mm²) | g/mm³ | m/s | Consistent units |
|
|
1036
|
+
+-----------------+--------------+------------+-----------------------+
|
|
1037
|
+
|
|
1038
|
+
References
|
|
1039
|
+
----------
|
|
1040
|
+
.. [2] J. W. Jaeken, S. Cottenier, Solving the Christoffel equation: Phase and group velocities, Computer Physics
|
|
1041
|
+
Communications (207), 2016, https://doi.org/10.1016/j.cpc.2016.06.014.
|
|
1042
|
+
|
|
1043
|
+
"""
|
|
1044
|
+
self._single_tensor_only('wave_velocity')
|
|
1045
|
+
def make_fun(index):
|
|
1046
|
+
def fun(n):
|
|
1047
|
+
Gamma = self.Christoffel_tensor(n)
|
|
1048
|
+
eig, _ = np.linalg.eig(Gamma)
|
|
1049
|
+
if index == 0:
|
|
1050
|
+
eig_of_interest = np.max(eig, axis=-1)
|
|
1051
|
+
elif index == 1:
|
|
1052
|
+
eig_of_interest = np.median(eig, axis=-1)
|
|
1053
|
+
else:
|
|
1054
|
+
eig_of_interest = np.min(eig, axis=-1)
|
|
1055
|
+
return np.sqrt(eig_of_interest / rho)
|
|
1056
|
+
|
|
1057
|
+
return fun
|
|
1058
|
+
|
|
1059
|
+
return [SphericalFunction(make_fun(i)) for i in range(3)]
|
|
1060
|
+
|
|
1061
|
+
@classmethod
|
|
1062
|
+
def from_MP(cls, ids, api_key=None):
|
|
1063
|
+
"""
|
|
1064
|
+
Import stiffness tensor(s) from the Materials Project API, given their material ids.
|
|
1065
|
+
|
|
1066
|
+
You need to register to `<https://materialsproject.org>`_ first to get an API key. This key can be explicitly
|
|
1067
|
+
passed as an argument (see below), or provided as an environment variable named MP_API_KEY.
|
|
1068
|
+
|
|
1069
|
+
Parameters
|
|
1070
|
+
----------
|
|
1071
|
+
ids : str or list of str
|
|
1072
|
+
ID(s) of the material to import (e.g. "mp-1048")
|
|
1073
|
+
api_key : str, optional
|
|
1074
|
+
API key to the Materials Project API. If not provided, it should be available as the API_KEY environment
|
|
1075
|
+
variable.
|
|
1076
|
+
|
|
1077
|
+
Returns
|
|
1078
|
+
-------
|
|
1079
|
+
list of StiffnessTensor
|
|
1080
|
+
If one of the requested material ids was not found, the corresponding value in the list will be None.
|
|
1081
|
+
"""
|
|
1082
|
+
try:
|
|
1083
|
+
from mp_api.client import MPRester
|
|
1084
|
+
except ImportError:
|
|
1085
|
+
raise ModuleNotFoundError('mp_api module is required for this function.')
|
|
1086
|
+
if type(ids) is str:
|
|
1087
|
+
Cdict = dict.fromkeys([ids])
|
|
1088
|
+
else:
|
|
1089
|
+
Cdict = dict.fromkeys(ids)
|
|
1090
|
+
with MPRester(api_key=api_key) as mpr:
|
|
1091
|
+
elasticity_doc = mpr.materials.elasticity.search(material_ids=ids)
|
|
1092
|
+
for material in elasticity_doc:
|
|
1093
|
+
key = str(material.material_id)
|
|
1094
|
+
if material.elastic_tensor is not None:
|
|
1095
|
+
matrix = material.elastic_tensor.ieee_format
|
|
1096
|
+
symmetry = material.symmetry.crystal_system.value
|
|
1097
|
+
phase_name = material.formula_pretty
|
|
1098
|
+
C = StiffnessTensor(np.asarray(matrix), symmetry=str(symmetry), phase_name=phase_name)
|
|
1099
|
+
else:
|
|
1100
|
+
C = None
|
|
1101
|
+
Cdict[key] = C
|
|
1102
|
+
if elasticity_doc:
|
|
1103
|
+
if isinstance(ids, str):
|
|
1104
|
+
return C
|
|
1105
|
+
else:
|
|
1106
|
+
return [Cdict[id] for id in ids]
|
|
1107
|
+
else:
|
|
1108
|
+
return None
|
|
1109
|
+
|
|
1110
|
+
@classmethod
|
|
1111
|
+
def weighted_average(cls, Cs, volume_fractions, method):
|
|
1112
|
+
"""
|
|
1113
|
+
Compute the weighted average of a list of stiffness tensors, with respect to a given method (Voigt, Reuss or
|
|
1114
|
+
Hill).
|
|
1115
|
+
|
|
1116
|
+
Parameters
|
|
1117
|
+
----------
|
|
1118
|
+
Cs : list of StiffnessTensor or list of ComplianceTensor or tuple of StiffnessTensor or tuple of ComplianceTensor
|
|
1119
|
+
Series of tensors to compute the average from
|
|
1120
|
+
volume_fractions : iterable of floats
|
|
1121
|
+
Volume fractions of each phase
|
|
1122
|
+
method : str, {'Voigt', 'Reuss', 'Hill'}
|
|
1123
|
+
Method to use. It can be 'Voigt', 'Reuss', or 'Hill'.
|
|
1124
|
+
|
|
1125
|
+
Returns
|
|
1126
|
+
-------
|
|
1127
|
+
StiffnessTensor
|
|
1128
|
+
Average tensor
|
|
1129
|
+
"""
|
|
1130
|
+
if np.all([isinstance(a, ComplianceTensor) for a in Cs]):
|
|
1131
|
+
Cs = [C.inv() for C in Cs]
|
|
1132
|
+
if np.all([isinstance(a, StiffnessTensor) for a in Cs]):
|
|
1133
|
+
C_stack = np.array([C._matrix for C in Cs])
|
|
1134
|
+
method = method.capitalize()
|
|
1135
|
+
if method == 'Voigt':
|
|
1136
|
+
C_avg = np.average(C_stack, weights=volume_fractions, axis=0)
|
|
1137
|
+
return StiffnessTensor(C_avg)
|
|
1138
|
+
elif method == 'Reuss':
|
|
1139
|
+
S_stack = np.linalg.inv(C_stack)
|
|
1140
|
+
S_avg = np.average(S_stack, weights=volume_fractions, axis=0)
|
|
1141
|
+
return StiffnessTensor(np.linalg.inv(S_avg))
|
|
1142
|
+
elif method == 'Hill':
|
|
1143
|
+
C_voigt = cls.weighted_average(Cs, volume_fractions, 'Voigt')
|
|
1144
|
+
C_reuss = cls.weighted_average(Cs, volume_fractions, 'Reuss')
|
|
1145
|
+
return (C_voigt + C_reuss) * 0.5
|
|
1146
|
+
else:
|
|
1147
|
+
raise ValueError('Method must be either Voigt, Reuss or Hill.')
|
|
1148
|
+
else:
|
|
1149
|
+
raise ValueError('The first argument must be either a list of ComplianceTensors or '
|
|
1150
|
+
'a list of StiffnessTensor.')
|
|
1151
|
+
|
|
1152
|
+
@property
|
|
1153
|
+
def universal_anisotropy(self):
|
|
1154
|
+
"""
|
|
1155
|
+
Compute the universal anisotropy factor.
|
|
1156
|
+
|
|
1157
|
+
The larger the value, the more likely the material will behave in an anisotropic way.
|
|
1158
|
+
|
|
1159
|
+
Returns
|
|
1160
|
+
-------
|
|
1161
|
+
float
|
|
1162
|
+
The universal anisotropy factor.
|
|
1163
|
+
|
|
1164
|
+
Notes
|
|
1165
|
+
-----
|
|
1166
|
+
The universal anisotropy factor is defined as [3]_:
|
|
1167
|
+
|
|
1168
|
+
.. math::
|
|
1169
|
+
|
|
1170
|
+
5\\frac{G_v}{G_r} + \\frac{K_v}{K_r} - 6
|
|
1171
|
+
|
|
1172
|
+
References
|
|
1173
|
+
----------
|
|
1174
|
+
.. [3] S. I. Ranganathan and M. Ostoja-Starzewski, Universal Elastic Anisotropy Index,
|
|
1175
|
+
*Phys. Rev. Lett.*, 101(5), 055504, 2008. https://doi.org/10.1103/PhysRevLett.101.055504
|
|
1176
|
+
"""
|
|
1177
|
+
self._single_tensor_only('universal_anisotropy')
|
|
1178
|
+
Cvoigt = self.Voigt_average()
|
|
1179
|
+
Creuss = self.Reuss_average()
|
|
1180
|
+
Gv = Cvoigt._matrix[3, 3]
|
|
1181
|
+
Gr = Creuss._matrix[3, 3]
|
|
1182
|
+
Kv = Cvoigt.bulk_modulus
|
|
1183
|
+
Kr = Creuss.bulk_modulus
|
|
1184
|
+
return 5 * Gv / Gr + Kv / Kr - 6
|
|
1185
|
+
|
|
1186
|
+
def Zener_ratio(self, tol=1e-4):
|
|
1187
|
+
"""
|
|
1188
|
+
Compute the Zener ratio (Z). Only valid for cubic symmetry.
|
|
1189
|
+
|
|
1190
|
+
This function first checks that the tensor has cubic symmetry within a given tolerance. If not, an error is
|
|
1191
|
+
raised.
|
|
1192
|
+
|
|
1193
|
+
Parameters
|
|
1194
|
+
----------
|
|
1195
|
+
tol : float, optional
|
|
1196
|
+
Tolerance to consider that the material has cubic symmetry
|
|
1197
|
+
|
|
1198
|
+
Returns
|
|
1199
|
+
-------
|
|
1200
|
+
float
|
|
1201
|
+
Zener ratio (NaN is the symmetry is not cubic)
|
|
1202
|
+
|
|
1203
|
+
Notes
|
|
1204
|
+
-----
|
|
1205
|
+
If the tensor is written in canonical base with Voigt mapping, the Zener ratio is defined as:
|
|
1206
|
+
|
|
1207
|
+
.. math::
|
|
1208
|
+
|
|
1209
|
+
Z=\\frac{ 2C_{44} }{C_{11} - C_{12}}
|
|
1210
|
+
|
|
1211
|
+
The present implementation takes advantage of eigenstiffness to compute the Zener ratio in *any* base, i.e. even
|
|
1212
|
+
if the tensor is not given in canonical base (e.g. if rotated).
|
|
1213
|
+
|
|
1214
|
+
See Also
|
|
1215
|
+
--------
|
|
1216
|
+
universal_anisotropy : compute the universal anisotropy factor
|
|
1217
|
+
eig_stiffnesses : eigenstiffnesses of the tensor
|
|
1218
|
+
is_cubic : check whether the tensor has cubic symmetry or not
|
|
1219
|
+
|
|
1220
|
+
Examples
|
|
1221
|
+
--------
|
|
1222
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor
|
|
1223
|
+
>>> C = StiffnessTensor.cubic(C11=200, C12=40, C44=20)
|
|
1224
|
+
>>> C.Zener_ratio()
|
|
1225
|
+
0.25
|
|
1226
|
+
|
|
1227
|
+
which obvisouly corresponds to 2.C44/(C11-C12).
|
|
1228
|
+
|
|
1229
|
+
Now, rotate the tensor and see how it looks like:
|
|
1230
|
+
|
|
1231
|
+
>>> from scipy.spatial.transform import Rotation
|
|
1232
|
+
>>> g = Rotation.from_euler('Z', 30, degrees=True)
|
|
1233
|
+
>>> C_rot = C*g
|
|
1234
|
+
>>> C_rot
|
|
1235
|
+
Stiffness tensor (in Voigt mapping):
|
|
1236
|
+
[[155. 85. 40. 0. 0.
|
|
1237
|
+
25.98076211]
|
|
1238
|
+
[ 85. 155. 40. 0. 0.
|
|
1239
|
+
-25.98076211]
|
|
1240
|
+
[ 40. 40. 200. 0. 0.
|
|
1241
|
+
0. ]
|
|
1242
|
+
[ 0. 0. 0. 20. 0.
|
|
1243
|
+
0. ]
|
|
1244
|
+
[ 0. 0. 0. 0. 20.
|
|
1245
|
+
0. ]
|
|
1246
|
+
[ 25.98076211 -25.98076211 0. 0. 0.
|
|
1247
|
+
65. ]]
|
|
1248
|
+
Symmetry: cubic
|
|
1249
|
+
|
|
1250
|
+
Still, we have
|
|
1251
|
+
>>> C_rot.Zener_ratio()
|
|
1252
|
+
0.24999999999999983
|
|
1253
|
+
"""
|
|
1254
|
+
if self.is_isotropic():
|
|
1255
|
+
return 1.0
|
|
1256
|
+
elif self.is_cubic():
|
|
1257
|
+
eigs, orders = self.eig_stiffnesses_multiplicity(1e-4)
|
|
1258
|
+
numer = eigs[orders==3][0] # 2*C44
|
|
1259
|
+
denom = eigs[orders==2][0] # C11-C12
|
|
1260
|
+
return numer / denom
|
|
1261
|
+
else:
|
|
1262
|
+
raise ValueError('The tensor does not seem to have cubic symmetry within the given tolerance ({})'.format(tol))
|
|
1263
|
+
|
|
1264
|
+
def to_pymatgen(self):
|
|
1265
|
+
"""
|
|
1266
|
+
Convert the stiffness tensor (from Elasticipy) to Python Materials Genomics (Pymatgen) format.
|
|
1267
|
+
|
|
1268
|
+
Returns
|
|
1269
|
+
-------
|
|
1270
|
+
pymatgen.analysis.elasticity.elastic.ElasticTensor
|
|
1271
|
+
Stiffness tensor for pymatgen
|
|
1272
|
+
"""
|
|
1273
|
+
try:
|
|
1274
|
+
from pymatgen.analysis.elasticity import elastic as matgenElast
|
|
1275
|
+
except ImportError:
|
|
1276
|
+
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
1277
|
+
return matgenElast.ElasticTensor(self.full_tensor())
|
|
1278
|
+
|
|
1279
|
+
def to_Kelvin(self):
|
|
1280
|
+
"""
|
|
1281
|
+
Returns all the tensor components using the Kelvin(-Mandel) mapping convention.
|
|
1282
|
+
|
|
1283
|
+
Returns
|
|
1284
|
+
-------
|
|
1285
|
+
numpy.ndarray
|
|
1286
|
+
(6,6) matrix, according to the Kelvin mapping
|
|
1287
|
+
|
|
1288
|
+
See Also
|
|
1289
|
+
--------
|
|
1290
|
+
eig : returns the eigenvalues and the eigenvectors of the Kelvin's matrix
|
|
1291
|
+
from_Kelvin : Construct a fourth-order tensor from its (6,6) Kelvin matrix
|
|
1292
|
+
|
|
1293
|
+
Notes
|
|
1294
|
+
-----
|
|
1295
|
+
This mapping convention is defined as follows [4]_:
|
|
1296
|
+
|
|
1297
|
+
.. math::
|
|
1298
|
+
|
|
1299
|
+
C_K = \\begin{bmatrix}
|
|
1300
|
+
C_{11} & C_{12} & C_{13} & \\sqrt{2}C_{14} & \\sqrt{2}C_{15} & \\sqrt{2}C_{16}\\\\
|
|
1301
|
+
C_{12} & C_{22} & C_{23} & \\sqrt{2}C_{24} & \\sqrt{2}C_{25} & \\sqrt{2}C_{26}\\\\
|
|
1302
|
+
C_{13} & C_{23} & C_{33} & \\sqrt{2}C_{34} & \\sqrt{2}C_{35} & \\sqrt{2}C_{36}\\\\
|
|
1303
|
+
\\sqrt{2}C_{14} & \\sqrt{2}C_{24} & \\sqrt{2}C_{34} & 2C_{44} & 2C_{45} & 2C_{46}\\\\
|
|
1304
|
+
\\sqrt{2}C_{15} & \\sqrt{2}C_{25} & \\sqrt{2}C_{35} & 2C_{45} & 2C_{55} & 2C_{56}\\\\
|
|
1305
|
+
\\sqrt{2}C_{16} & \\sqrt{2}C_{26} & \\sqrt{2}C_{36} & 2C_{46} & 2C_{56} & 2C_{66}\\\\
|
|
1306
|
+
\\end{bmatrix}
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
References
|
|
1310
|
+
----------
|
|
1311
|
+
.. [4] Helbig, K. (2013). What Kelvin might have written about Elasticity. Geophysical Prospecting, 61(1), 1-20.
|
|
1312
|
+
doi: 10.1111/j.1365-2478.2011.01049.x
|
|
1313
|
+
"""
|
|
1314
|
+
kelvin_mapping = KelvinMapping()
|
|
1315
|
+
return self._matrix /self.mapping.matrix * kelvin_mapping.matrix
|
|
1316
|
+
|
|
1317
|
+
def eig(self):
|
|
1318
|
+
"""
|
|
1319
|
+
Compute the eigenstiffnesses and the eigenstrains.
|
|
1320
|
+
|
|
1321
|
+
Solve the eigenvalue problem from the Kelvin matrix of the stiffness tensor (see Notes).
|
|
1322
|
+
|
|
1323
|
+
Returns
|
|
1324
|
+
-------
|
|
1325
|
+
numpy.ndarray
|
|
1326
|
+
Array of 6 eigenstiffnesses (eigenvalues of the stiffness matrix)
|
|
1327
|
+
numpy.ndarray
|
|
1328
|
+
(6,6) array of eigenstrains (eigenvectors of the stiffness matrix)
|
|
1329
|
+
|
|
1330
|
+
See Also
|
|
1331
|
+
--------
|
|
1332
|
+
to_Kelvin : returns the stiffness components as a (6,6) matrix, according to the Kelvin mapping convention.
|
|
1333
|
+
eig_stiffnesses : returns the eigenstiffnesses only
|
|
1334
|
+
eig_strains : returns the eigenstrains only
|
|
1335
|
+
|
|
1336
|
+
Notes
|
|
1337
|
+
-----
|
|
1338
|
+
The definition for eigenstiffnesses and the eigenstrains are introduced in [4]_.
|
|
1339
|
+
"""
|
|
1340
|
+
return np.linalg.eigh(self.to_Kelvin())
|
|
1341
|
+
|
|
1342
|
+
@property
|
|
1343
|
+
def eig_stiffnesses(self):
|
|
1344
|
+
"""
|
|
1345
|
+
Compute the eigenstiffnesses given by the Kelvin's matrix for stiffness.
|
|
1346
|
+
|
|
1347
|
+
Returns
|
|
1348
|
+
-------
|
|
1349
|
+
numpy.ndarray
|
|
1350
|
+
6 eigenvalues of the Kelvin's stiffness matrix, in ascending order
|
|
1351
|
+
|
|
1352
|
+
See Also
|
|
1353
|
+
--------
|
|
1354
|
+
eig : returns the eigenstiffnesses and the eigenstrains
|
|
1355
|
+
eig_strains : returns the eigenstrains only
|
|
1356
|
+
eig_stiffnesses_multiplicity : returns the unique values of eigenstiffnesses with multiplicity
|
|
1357
|
+
"""
|
|
1358
|
+
return np.linalg.eigvalsh(self.to_Kelvin())
|
|
1359
|
+
|
|
1360
|
+
@property
|
|
1361
|
+
def eig_strains(self):
|
|
1362
|
+
"""
|
|
1363
|
+
Compute the eigenstrains from the Kelvin's matrix for stiffness
|
|
1364
|
+
|
|
1365
|
+
Returns
|
|
1366
|
+
-------
|
|
1367
|
+
numpy.ndarray
|
|
1368
|
+
(6,6) matrix of eigenstrains, sorted by ascending order of eigenstiffnesses.
|
|
1369
|
+
|
|
1370
|
+
See Also
|
|
1371
|
+
--------
|
|
1372
|
+
eig : returns both the eigenvalues and the eigenvectors of the Kelvin matrix
|
|
1373
|
+
"""
|
|
1374
|
+
return self.eig()[1]
|
|
1375
|
+
|
|
1376
|
+
@property
|
|
1377
|
+
def eig_compliances(self):
|
|
1378
|
+
"""
|
|
1379
|
+
Compute the eigencompliances from the Kelvin's matrix of stiffness
|
|
1380
|
+
|
|
1381
|
+
Returns
|
|
1382
|
+
-------
|
|
1383
|
+
numpy.ndarray
|
|
1384
|
+
Inverses of the 6 eigenvalues of the Kelvin's stiffness matrix, in descending order
|
|
1385
|
+
|
|
1386
|
+
See Also
|
|
1387
|
+
--------
|
|
1388
|
+
eig_stiffnesses : compute the eigenstiffnesses from the Kelvin's matrix of stiffness
|
|
1389
|
+
"""
|
|
1390
|
+
return 1/self.eig_stiffnesses
|
|
1391
|
+
|
|
1392
|
+
@classmethod
|
|
1393
|
+
def from_Kelvin(cls, matrix, **kwargs):
|
|
1394
|
+
"""
|
|
1395
|
+
Create a tensor from the (6,6) matrix following the Kelvin(-Mandel) mapping convention
|
|
1396
|
+
|
|
1397
|
+
Parameters
|
|
1398
|
+
----------
|
|
1399
|
+
matrix : list or numpy.ndarray
|
|
1400
|
+
(6,6) matrix of components
|
|
1401
|
+
kwargs
|
|
1402
|
+
keyword arguments passed to the constructor
|
|
1403
|
+
|
|
1404
|
+
Returns
|
|
1405
|
+
-------
|
|
1406
|
+
StiffnessTensor
|
|
1407
|
+
|
|
1408
|
+
See Also
|
|
1409
|
+
--------
|
|
1410
|
+
to_Kelvin : return the components as a (6,6) matrix following the Kelvin convention
|
|
1411
|
+
"""
|
|
1412
|
+
kelvin_mapping = KelvinMapping()
|
|
1413
|
+
t = cls(matrix / kelvin_mapping.matrix, **kwargs)
|
|
1414
|
+
t._matrix *= t.mapping.matrix
|
|
1415
|
+
return t
|
|
1416
|
+
|
|
1417
|
+
def eig_stiffnesses_multiplicity(self, tol=1e-4):
|
|
1418
|
+
"""
|
|
1419
|
+
Compute the eigenstiffnesses, then returns the multiplicity of each eigenstiffness.
|
|
1420
|
+
|
|
1421
|
+
Given an absolute tolerance, duplicates in eigenstiffnesses are considered to compute the multiplicity of each
|
|
1422
|
+
value.
|
|
1423
|
+
|
|
1424
|
+
Parameters
|
|
1425
|
+
----------
|
|
1426
|
+
tol : float, optional
|
|
1427
|
+
Absolute tolerance to assume that two distinct eigenstiffnesses are the same
|
|
1428
|
+
|
|
1429
|
+
Returns
|
|
1430
|
+
-------
|
|
1431
|
+
numpy.ndarray
|
|
1432
|
+
Unique values of eigenstiffnesses, sorted by increasing multiplicity
|
|
1433
|
+
numpy.ndarray
|
|
1434
|
+
Multiplicity of each unique eigenstiffness, sorted by ascending value
|
|
1435
|
+
|
|
1436
|
+
See Also
|
|
1437
|
+
--------
|
|
1438
|
+
eig_stiffnesses : compute the eigenstiffnesses
|
|
1439
|
+
|
|
1440
|
+
Examples
|
|
1441
|
+
--------
|
|
1442
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor
|
|
1443
|
+
>>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
|
|
1444
|
+
>>> C.eig_stiffnesses
|
|
1445
|
+
array([ 52., 52., 154., 154., 154., 454.])
|
|
1446
|
+
>>> C.eig_stiffnesses_multiplicity()
|
|
1447
|
+
(array([ 52., 154., 454.]), array([2, 3, 1]))
|
|
1448
|
+
"""
|
|
1449
|
+
eig = self.eig_stiffnesses
|
|
1450
|
+
counts = []
|
|
1451
|
+
uniques = []
|
|
1452
|
+
while eig.size:
|
|
1453
|
+
duplicates = np.isclose(eig[0], eig, atol=tol)
|
|
1454
|
+
counts.append(np.count_nonzero(duplicates))
|
|
1455
|
+
uniques.append(eig[0])
|
|
1456
|
+
eig = eig[np.logical_not(duplicates)]
|
|
1457
|
+
return np.array(uniques), np.array(counts)
|
|
1458
|
+
|
|
1459
|
+
def _check_eig_signature(self, signature, tol):
|
|
1460
|
+
if self.shape:
|
|
1461
|
+
flat = self.flatten()
|
|
1462
|
+
bool_array = np.zeros(flat.shape[0], dtype=bool)
|
|
1463
|
+
for i, t in enumerate(flat):
|
|
1464
|
+
_, order = t.eig_stiffnesses_multiplicity(tol=tol)
|
|
1465
|
+
bool_array[i] = np.array_equal(np.sort(order), np.sort(signature))
|
|
1466
|
+
return bool_array.reshape(self.shape)
|
|
1467
|
+
else:
|
|
1468
|
+
_, order = self.eig_stiffnesses_multiplicity(tol=tol)
|
|
1469
|
+
return np.array_equal(np.sort(order), np.sort(signature))
|
|
1470
|
+
|
|
1471
|
+
def is_isotropic(self, tol=0.01):
|
|
1472
|
+
"""Check that the tensor corresponds to isotropic symmetry, within a given tolerance.
|
|
1473
|
+
|
|
1474
|
+
The method relies on the multiplicity of eigenstiffnesses
|
|
1475
|
+
|
|
1476
|
+
Parameters
|
|
1477
|
+
----------
|
|
1478
|
+
tol : float
|
|
1479
|
+
Absolute tolerance to consider multiplicity of eigenstiffnesses
|
|
1480
|
+
|
|
1481
|
+
Returns
|
|
1482
|
+
-------
|
|
1483
|
+
bool or numpy.ndarray
|
|
1484
|
+
If the tensor is single, the returned value is boolean.
|
|
1485
|
+
If the object is a tensor array, the returned value is an array of bools, the same shape as the tensor array.
|
|
1486
|
+
|
|
1487
|
+
See Also
|
|
1488
|
+
--------
|
|
1489
|
+
is_cubic : check if the stiffness tensor has cubic symmetry
|
|
1490
|
+
is_tetragonal : check if the stiffness tensor has tetragonal symmetry
|
|
1491
|
+
eig_stiffnesses : compute eigenstiffnesses
|
|
1492
|
+
"""
|
|
1493
|
+
return self._check_eig_signature([1, 5], tol)
|
|
1494
|
+
|
|
1495
|
+
def is_cubic(self, tol=0.01):
|
|
1496
|
+
"""Check that the tensor corresponds to cubic symmetry, within a given tolerance.
|
|
1497
|
+
|
|
1498
|
+
The method relies on the multiplicity of eigenstiffnesses.
|
|
1499
|
+
|
|
1500
|
+
Parameters
|
|
1501
|
+
----------
|
|
1502
|
+
tol : float, optional
|
|
1503
|
+
Absolute tolerance to consider multiplicity of eigenstiffnesses
|
|
1504
|
+
|
|
1505
|
+
Returns
|
|
1506
|
+
-------
|
|
1507
|
+
bool or numpy.ndarray
|
|
1508
|
+
If the tensor is single, the returned value is boolean.
|
|
1509
|
+
If the object is a tensor array, the returned value is an array of bools, the same shape as the tensor array.
|
|
1510
|
+
|
|
1511
|
+
See Also
|
|
1512
|
+
--------
|
|
1513
|
+
is_isotropic : check if the stiffness tensor is isotropic
|
|
1514
|
+
is_tetragonal : check if the stiffness tensor has tetragonal symmetry
|
|
1515
|
+
eig_stiffnesses : compute eigenstiffnesses
|
|
1516
|
+
|
|
1517
|
+
Examples
|
|
1518
|
+
--------
|
|
1519
|
+
>>> from Elasticipy.tensors.elasticity import StiffnessTensor
|
|
1520
|
+
>>> from scipy.spatial.transform import Rotation
|
|
1521
|
+
>>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
|
|
1522
|
+
>>> C_rotated = C * Rotation.random(random_state=123)
|
|
1523
|
+
>>> C_rotated
|
|
1524
|
+
Stiffness tensor (in Voigt mapping):
|
|
1525
|
+
[[237.71171578 96.41409344 119.87419078 8.1901353 -3.63846312
|
|
1526
|
+
-20.34233446]
|
|
1527
|
+
[ 96.41409344 250.74909842 106.83680814 9.33462785 -6.52548033
|
|
1528
|
+
0.99714278]
|
|
1529
|
+
[119.87419078 106.83680814 227.28900108 -17.52476315 10.16394345
|
|
1530
|
+
19.34519167]
|
|
1531
|
+
[ 8.1901353 9.33462785 -17.52476315 49.83680814 19.34519167
|
|
1532
|
+
-6.52548033]
|
|
1533
|
+
[ -3.63846312 -6.52548033 10.16394345 19.34519167 62.87419078
|
|
1534
|
+
8.1901353 ]
|
|
1535
|
+
[-20.34233446 0.99714278 19.34519167 -6.52548033 8.1901353
|
|
1536
|
+
39.41409344]]
|
|
1537
|
+
Symmetry: cubic
|
|
1538
|
+
|
|
1539
|
+
Once rotated, it is not clear if the stiffness tensors has cubic symmetry. Yet:
|
|
1540
|
+
>>> C_rotated.is_cubic()
|
|
1541
|
+
True
|
|
1542
|
+
|
|
1543
|
+
"""
|
|
1544
|
+
return np.logical_or(self._check_eig_signature([1, 2, 3], tol), self.is_isotropic(tol=tol))
|
|
1545
|
+
|
|
1546
|
+
def is_tetragonal(self, tol=0.01):
|
|
1547
|
+
"""Check that the tensor corresponds to tetragonal symmetry, within a given tolerance.
|
|
1548
|
+
|
|
1549
|
+
The method relies on the multiplicity of eigenstiffnesses.
|
|
1550
|
+
|
|
1551
|
+
Parameters
|
|
1552
|
+
----------
|
|
1553
|
+
tol : float
|
|
1554
|
+
Absolute tolerance to consider multiplicity of eigenstiffnesses
|
|
1555
|
+
|
|
1556
|
+
Returns
|
|
1557
|
+
-------
|
|
1558
|
+
bool or numpy.ndarray
|
|
1559
|
+
If the tensor is single, the returned value is boolean.
|
|
1560
|
+
If the object is a tensor array, the returned value is an array of bools, the same shape as the tensor array.
|
|
1561
|
+
|
|
1562
|
+
See Also
|
|
1563
|
+
--------
|
|
1564
|
+
is_isotropic : check if the stiffness tensor is isotropic
|
|
1565
|
+
is_cubic : check if the stiffness tensor has cubic symmetry
|
|
1566
|
+
eig_stiffnesses : compute eigenstiffnesses
|
|
1567
|
+
"""
|
|
1568
|
+
return np.logical_or(self._check_eig_signature([1, 1, 1, 1, 2], tol), self.is_cubic(tol=tol))
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
class ComplianceTensor(StiffnessTensor):
|
|
1572
|
+
"""
|
|
1573
|
+
Class for manipulating compliance tensors
|
|
1574
|
+
"""
|
|
1575
|
+
tensor_name = 'Compliance'
|
|
1576
|
+
C11_C12_factor = 2.0
|
|
1577
|
+
component_prefix = 'S'
|
|
1578
|
+
C46_C56_factor = 2.0
|
|
1579
|
+
|
|
1580
|
+
def __init__(self, C, check_positive_definite=True, mapping=VoigtMapping(tensor='Compliance'), **kwargs):
|
|
1581
|
+
super().__init__(C, check_positive_definite=check_positive_definite, mapping=mapping, **kwargs)
|
|
1582
|
+
self.mapping_name = 'Voigt'
|
|
1583
|
+
|
|
1584
|
+
def __mul__(self, other):
|
|
1585
|
+
if isinstance(other, StressTensor):
|
|
1586
|
+
new_tensor = self.ddot(other)
|
|
1587
|
+
return StrainTensor(new_tensor.matrix)
|
|
1588
|
+
elif isinstance(other, StrainTensor):
|
|
1589
|
+
raise ValueError('You cannot multiply a compliance tensor with Strain tensor.')
|
|
1590
|
+
else:
|
|
1591
|
+
return super().__mul__(other)
|
|
1592
|
+
|
|
1593
|
+
def inv(self):
|
|
1594
|
+
"""
|
|
1595
|
+
Compute the reciprocal stiffness tensor
|
|
1596
|
+
|
|
1597
|
+
Returns
|
|
1598
|
+
-------
|
|
1599
|
+
StiffnessTensor
|
|
1600
|
+
Reciprocal tensor
|
|
1601
|
+
"""
|
|
1602
|
+
S = np.linalg.inv(self._matrix)
|
|
1603
|
+
return StiffnessTensor(S, symmetry=self.symmetry, phase_name=self.phase_name)
|
|
1604
|
+
|
|
1605
|
+
def Reuss_average(self, axis=None):
|
|
1606
|
+
if self.ndim:
|
|
1607
|
+
return self.mean(axis=axis)
|
|
1608
|
+
else:
|
|
1609
|
+
s = self._matrix
|
|
1610
|
+
A = s[0, 0] + s[1, 1] + s[2, 2]
|
|
1611
|
+
B = s[0, 1] + s[0, 2] + s[1, 2]
|
|
1612
|
+
C = s[3, 3] + s[4, 4] + s[5, 5]
|
|
1613
|
+
S11 = 1 / 5 * A + 2 / 15 * B + 1 / 15 * C
|
|
1614
|
+
S12 = 1 / 15 * A + 4 / 15 * B - 1 / 30 * C
|
|
1615
|
+
S44 = 4 / 15 * (A - B) + 1 / 5 * C
|
|
1616
|
+
mat = _isotropic_matrix(S11, S12, S44)
|
|
1617
|
+
return ComplianceTensor(mat, symmetry='isotropic', phase_name=self.phase_name)
|
|
1618
|
+
|
|
1619
|
+
def Voigt_average(self, axis=None):
|
|
1620
|
+
return self.inv().Voigt_average(axis=axis).inv()
|
|
1621
|
+
|
|
1622
|
+
def Hill_average(self, axis=None):
|
|
1623
|
+
return self.inv().Hill_average(axis=axis).inv()
|
|
1624
|
+
|
|
1625
|
+
@classmethod
|
|
1626
|
+
def isotropic(cls, E=None, nu=None, lame1=None, lame2=None, phase_name=None):
|
|
1627
|
+
return super().isotropic(E=E, nu=nu, lame1=lame1, lame2=lame2, phase_name=None).inv()
|
|
1628
|
+
|
|
1629
|
+
@classmethod
|
|
1630
|
+
def orthotropic(cls, *args, **kwargs):
|
|
1631
|
+
return super().orthotropic(*args, **kwargs).inv()
|
|
1632
|
+
|
|
1633
|
+
@classmethod
|
|
1634
|
+
def transverse_isotropic(cls, *args, **kwargs):
|
|
1635
|
+
return super().transverse_isotropic(*args, **kwargs).inv()
|
|
1636
|
+
|
|
1637
|
+
@classmethod
|
|
1638
|
+
def weighted_average(cls, *args):
|
|
1639
|
+
return super().weighted_average(*args).inv()
|
|
1640
|
+
|
|
1641
|
+
@property
|
|
1642
|
+
def bulk_modulus(self):
|
|
1643
|
+
matrix_t = self._matrix.T
|
|
1644
|
+
sub_matrix = matrix_t[0:3, 0:3]
|
|
1645
|
+
return 1 / np.sum(sub_matrix, axis=(0,1))
|
|
1646
|
+
|
|
1647
|
+
@property
|
|
1648
|
+
def universal_anisotropy(self):
|
|
1649
|
+
"""
|
|
1650
|
+
Compute the universal anisotropy factor.
|
|
1651
|
+
|
|
1652
|
+
It is actually an alias for inv().universal_anisotropy.
|
|
1653
|
+
|
|
1654
|
+
Returns
|
|
1655
|
+
-------
|
|
1656
|
+
float
|
|
1657
|
+
Universal anisotropy factor
|
|
1658
|
+
"""
|
|
1659
|
+
return self.inv().universal_anisotropy
|
|
1660
|
+
|
|
1661
|
+
def to_pymatgen(self):
|
|
1662
|
+
"""
|
|
1663
|
+
Convert the compliance tensor (from Elasticipy) to Python Materials Genomics (Pymatgen) format.
|
|
1664
|
+
|
|
1665
|
+
Returns
|
|
1666
|
+
-------
|
|
1667
|
+
ComplianceTensor
|
|
1668
|
+
Compliance tensor for pymatgen
|
|
1669
|
+
"""
|
|
1670
|
+
try:
|
|
1671
|
+
from pymatgen.analysis.elasticity import elastic as matgenElast
|
|
1672
|
+
except ImportError:
|
|
1673
|
+
raise ModuleNotFoundError('pymatgen module is required for this function.')
|
|
1674
|
+
return matgenElast.ComplianceTensor(self.full_tensor())
|
|
1675
|
+
|
|
1676
|
+
def eig(self):
|
|
1677
|
+
"""
|
|
1678
|
+
Compute the eigencompliances and the eigenstresses.
|
|
1679
|
+
|
|
1680
|
+
Solve the eigenvalue problem from the Kelvin matrix of the compliance tensor (see Notes).
|
|
1681
|
+
|
|
1682
|
+
Returns
|
|
1683
|
+
-------
|
|
1684
|
+
numpy.ndarray
|
|
1685
|
+
Array of 6 eigencompliances (eigenvalues of the stiffness matrix)
|
|
1686
|
+
numpy.ndarray
|
|
1687
|
+
(6,6) array of eigenstresses (eigenvectors of the stiffness matrix)
|
|
1688
|
+
|
|
1689
|
+
See Also
|
|
1690
|
+
--------
|
|
1691
|
+
Kelvin : returns the stiffness components as a (6,6) matrix, according to the Kelvin mapping convention.
|
|
1692
|
+
eig_compliances : returns the eigencompliances only
|
|
1693
|
+
eig_stresses : returns the eigenstresses only
|
|
1694
|
+
|
|
1695
|
+
Notes
|
|
1696
|
+
-----
|
|
1697
|
+
The definition for eigencompliances and the eigenstresses are introduced in [4]_.
|
|
1698
|
+
"""
|
|
1699
|
+
return np.linalg.eigh(self.to_Kelvin())
|
|
1700
|
+
|
|
1701
|
+
@property
|
|
1702
|
+
def eig_compliances(self):
|
|
1703
|
+
"""
|
|
1704
|
+
Compute the eigencompliances given by the Kelvin's matrix for stiffness.
|
|
1705
|
+
|
|
1706
|
+
Returns
|
|
1707
|
+
-------
|
|
1708
|
+
numpy.ndarray
|
|
1709
|
+
6 eigenvalues of the Kelvin's compliance matrix, in ascending order
|
|
1710
|
+
|
|
1711
|
+
See Also
|
|
1712
|
+
--------
|
|
1713
|
+
eig : returns the eigencompliances and the eigenstresses
|
|
1714
|
+
eig_strains : returns the eigenstresses only
|
|
1715
|
+
"""
|
|
1716
|
+
return np.linalg.eigvalsh(self.to_Kelvin())
|
|
1717
|
+
|
|
1718
|
+
@property
|
|
1719
|
+
def eig_stresses(self):
|
|
1720
|
+
"""
|
|
1721
|
+
Compute the eigenstresses from the Kelvin's matrix for stiffness
|
|
1722
|
+
|
|
1723
|
+
Returns
|
|
1724
|
+
-------
|
|
1725
|
+
numpy.ndarray
|
|
1726
|
+
(6,6) matrix of eigenstresses, sorted by ascending order of eigencompliances.
|
|
1727
|
+
|
|
1728
|
+
See Also
|
|
1729
|
+
--------
|
|
1730
|
+
eig : returns both the eigencompliances and the eigenstresses
|
|
1731
|
+
"""
|
|
1732
|
+
return self.eig()[1]
|
|
1733
|
+
|
|
1734
|
+
@property
|
|
1735
|
+
def eig_stiffnesses(self):
|
|
1736
|
+
"""
|
|
1737
|
+
Compute the eigenstiffnesses from the Kelvin's matrix of compliance
|
|
1738
|
+
|
|
1739
|
+
Returns
|
|
1740
|
+
-------
|
|
1741
|
+
numpy.ndarray
|
|
1742
|
+
inverses of 6 eigenvalues of the Kelvin's compliance matrix, in descending order
|
|
1743
|
+
|
|
1744
|
+
See Also
|
|
1745
|
+
--------
|
|
1746
|
+
eig_compliances : compute the eigencompliances from the Kelvin's matrix of compliance
|
|
1747
|
+
"""
|
|
1748
|
+
return 1/self.eig_compliances
|