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.
@@ -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