elasticipy 2.9.0__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Elasticipy/FourthOrderTensor.py +16 -1463
- Elasticipy/StressStrainTensors.py +16 -134
- Elasticipy/ThermalExpansion.py +12 -178
- Elasticipy/gui.py +2 -2
- Elasticipy/{Plasticity.py → plasticity.py} +106 -56
- Elasticipy/{SphericalFunction.py → spherical_function.py} +1 -1
- Elasticipy/tensors/__init__.py +0 -0
- Elasticipy/tensors/elasticity.py +1656 -0
- Elasticipy/tensors/fourth_order.py +591 -0
- Elasticipy/{SecondOrderTensor.py → tensors/second_order.py} +248 -59
- Elasticipy/tensors/stress_strain.py +138 -0
- Elasticipy/tensors/thermal_expansion.py +249 -0
- {elasticipy-2.9.0.dist-info → elasticipy-4.0.0.dist-info}/METADATA +6 -5
- elasticipy-4.0.0.dist-info/RECORD +20 -0
- {elasticipy-2.9.0.dist-info → elasticipy-4.0.0.dist-info}/WHEEL +1 -1
- elasticipy-2.9.0.dist-info/RECORD +0 -15
- /Elasticipy/{CrystalSymmetries.py → crystal_symmetries.py} +0 -0
- /Elasticipy/{PoleFigure.py → polefigure.py} +0 -0
- {elasticipy-2.9.0.dist-info → elasticipy-4.0.0.dist-info/licenses}/LICENSE +0 -0
- {elasticipy-2.9.0.dist-info → elasticipy-4.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
from scipy.spatial.transform import Rotation
|
|
4
|
-
|
|
6
|
+
ALPHABET = 'abcdefghijklmnopqrstuv'
|
|
5
7
|
|
|
6
8
|
class _MatrixProxy:
|
|
7
9
|
def __init__(self, matrix):
|
|
@@ -36,6 +38,45 @@ def _transpose_matrix(matrix):
|
|
|
36
38
|
def _symmetric_part(matrix):
|
|
37
39
|
return 0.5 * (matrix + _transpose_matrix(matrix))
|
|
38
40
|
|
|
41
|
+
def _orientation_shape(g):
|
|
42
|
+
if is_orix_rotation(g):
|
|
43
|
+
return g.shape
|
|
44
|
+
else:
|
|
45
|
+
return (len(g),)
|
|
46
|
+
|
|
47
|
+
def _is_single_rotation(rotation):
|
|
48
|
+
if isinstance(rotation, Rotation):
|
|
49
|
+
return rotation.single
|
|
50
|
+
elif is_orix_rotation(rotation):
|
|
51
|
+
return rotation.size == 1
|
|
52
|
+
else:
|
|
53
|
+
raise TypeError('The input argument must be of class scipy.transform.Rotation or '
|
|
54
|
+
'orix.quaternion.rotation.Rotation')
|
|
55
|
+
|
|
56
|
+
_voigt_numbering = [[0, 0], [1, 1], [2, 2], [1, 2], [0, 2], [0, 1]]
|
|
57
|
+
|
|
58
|
+
def _unmap(array, mapping_convention):
|
|
59
|
+
array = np.asarray(array)
|
|
60
|
+
shape = array.shape
|
|
61
|
+
if shape and (shape[-1] == 6):
|
|
62
|
+
new_shape = shape[:-1] + (3, 3)
|
|
63
|
+
unmapped_matrix = np.zeros(new_shape)
|
|
64
|
+
for i in range(6):
|
|
65
|
+
unmapped_matrix[..., _voigt_numbering[i][0], _voigt_numbering[i][1]] = array[..., i] / mapping_convention[i]
|
|
66
|
+
return unmapped_matrix
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError("array must be of shape (6,) or (...,6) with Voigt vector")
|
|
69
|
+
|
|
70
|
+
def _map(matrix, mapping_convention):
|
|
71
|
+
shape = matrix.shape[:-2] + (6,)
|
|
72
|
+
array = np.zeros(shape)
|
|
73
|
+
for i in range(6):
|
|
74
|
+
j, k = _voigt_numbering[i]
|
|
75
|
+
array[...,i] = matrix[...,j,k]
|
|
76
|
+
return array * mapping_convention
|
|
77
|
+
|
|
78
|
+
kelvin_mapping = [1, 1, 1, np.sqrt(2), np.sqrt(2), np.sqrt(2)]
|
|
79
|
+
|
|
39
80
|
class SecondOrderTensor:
|
|
40
81
|
"""
|
|
41
82
|
Template class for manipulation of second order tensors or arrays of second order tensors
|
|
@@ -251,7 +292,7 @@ class SecondOrderTensor:
|
|
|
251
292
|
I3 : Third invariant of the tensors (det)
|
|
252
293
|
"""
|
|
253
294
|
a = self.I1**2
|
|
254
|
-
b = np.matmul(self.matrix, self.
|
|
295
|
+
b = np.matmul(self.matrix, self._transpose_tensor()).trace(axis1=-1, axis2=-2)
|
|
255
296
|
return 0.5 * (a - b)
|
|
256
297
|
|
|
257
298
|
@property
|
|
@@ -391,13 +432,9 @@ class SecondOrderTensor:
|
|
|
391
432
|
matmul : matrix-like multiplication of tensor arrays
|
|
392
433
|
"""
|
|
393
434
|
if isinstance(B, SecondOrderTensor):
|
|
394
|
-
|
|
395
|
-
return SecondOrderTensor(new_mat)
|
|
435
|
+
return self.dot(B, mode='pair')
|
|
396
436
|
elif isinstance(B, Rotation) or is_orix_rotation(B):
|
|
397
|
-
|
|
398
|
-
new_matrix = np.matmul(np.matmul(transpose_matrices, self.matrix), rotation_matrices)
|
|
399
|
-
# In case of rotation, the property of the transformed tensor is kept
|
|
400
|
-
return self.__class__(new_matrix)
|
|
437
|
+
return self.rotate(B, mode='pair')
|
|
401
438
|
elif isinstance(B, (float, int)):
|
|
402
439
|
return self.__class__(self.matrix * B)
|
|
403
440
|
elif isinstance(B, np.ndarray):
|
|
@@ -413,6 +450,44 @@ class SecondOrderTensor:
|
|
|
413
450
|
else:
|
|
414
451
|
raise ValueError('The input argument must be a tensor, an ndarray, a rotation or a scalar value.')
|
|
415
452
|
|
|
453
|
+
def rotate(self, rotation, mode='pair'):
|
|
454
|
+
"""
|
|
455
|
+
Apply rotation(s) to the tensor(s).
|
|
456
|
+
|
|
457
|
+
The rotations can be applied element-wise, or on each cross-combination (see below).
|
|
458
|
+
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
rotation : scipy.spatial.Rotation or orix.quaternion.Rotation
|
|
462
|
+
mode : str, optional
|
|
463
|
+
If 'pair', the rotations are applied element wise. Broadcasting rule applies.
|
|
464
|
+
If 'cross', all the possible combinations are considered. If ``C=A.rotate(rot)``, then
|
|
465
|
+
``C.shape==A.shape + rot.shape``.
|
|
466
|
+
|
|
467
|
+
Returns
|
|
468
|
+
-------
|
|
469
|
+
SecondOrderTensor
|
|
470
|
+
"""
|
|
471
|
+
if self.shape == ():
|
|
472
|
+
ein_str = '...li,...kj,lk->...ij'
|
|
473
|
+
elif _is_single_rotation(rotation):
|
|
474
|
+
ein_str = 'li,kj,...lk->...ij'
|
|
475
|
+
else:
|
|
476
|
+
if mode=='pair':
|
|
477
|
+
ein_str = '...li,...kj,...lk->...ij'
|
|
478
|
+
elif mode=='cross':
|
|
479
|
+
ndim_0 = self.ndim
|
|
480
|
+
ndim_1 = len(_orientation_shape(rotation))
|
|
481
|
+
indices_self = ALPHABET[:ndim_0]
|
|
482
|
+
indices_g = ALPHABET[:ndim_1].upper()
|
|
483
|
+
indices_res = indices_self + indices_g
|
|
484
|
+
ein_str = indices_g + 'zw,' + indices_g + 'yx,' + indices_self + 'zy->' + indices_res + 'wx'
|
|
485
|
+
else:
|
|
486
|
+
raise ValueError('Invalid mode. It can be "cross" or "pair".')
|
|
487
|
+
g_mat = rotation_to_matrix(rotation)
|
|
488
|
+
matrix = np.einsum(ein_str, g_mat, g_mat, self.matrix)
|
|
489
|
+
return self.__class__(matrix)
|
|
490
|
+
|
|
416
491
|
def __rmul__(self, other):
|
|
417
492
|
if isinstance(other, (float, int)):
|
|
418
493
|
return self.__mul__(other)
|
|
@@ -431,7 +506,7 @@ class SecondOrderTensor:
|
|
|
431
506
|
raise NotImplementedError('Tensors can only be divided by scalar values or by arrays of the same shape.')
|
|
432
507
|
return self.__class__(new_mat)
|
|
433
508
|
|
|
434
|
-
def __eq__(self, other)
|
|
509
|
+
def __eq__(self, other):
|
|
435
510
|
"""
|
|
436
511
|
Check whether the tensors in the tensor array are equal
|
|
437
512
|
|
|
@@ -442,7 +517,7 @@ class SecondOrderTensor:
|
|
|
442
517
|
|
|
443
518
|
Returns
|
|
444
519
|
-------
|
|
445
|
-
|
|
520
|
+
numpy.ndarray
|
|
446
521
|
True element is True if the corresponding tensors are equal.
|
|
447
522
|
"""
|
|
448
523
|
if isinstance(other, SecondOrderTensor):
|
|
@@ -453,6 +528,73 @@ class SecondOrderTensor:
|
|
|
453
528
|
else:
|
|
454
529
|
raise ValueError('The value to compare must be an array of shape {} or {}'.format(self.shape, self.shape + (3,3)))
|
|
455
530
|
|
|
531
|
+
def dot(self, other, mode='pair'):
|
|
532
|
+
"""
|
|
533
|
+
Perform contraction product ("dot product") between tensor.
|
|
534
|
+
|
|
535
|
+
On tensor arrays, the product contraction can be performed element-wise, or considering all cross-combinations
|
|
536
|
+
(see below).
|
|
537
|
+
|
|
538
|
+
Parameters
|
|
539
|
+
----------
|
|
540
|
+
other : SecondOrderTensor
|
|
541
|
+
tensor or tensor array to compute the product from
|
|
542
|
+
mode : str, optional
|
|
543
|
+
If 'pair' (default), the contraction products of tensor arrays are applied element-wise. Broadcasting rule
|
|
544
|
+
applies.
|
|
545
|
+
|
|
546
|
+
If 'cross', all combinations of contraction product are considered. If ``C=A.dot(B,mode='cross')``, then
|
|
547
|
+
``C.shape==A.shape + B.shape``
|
|
548
|
+
|
|
549
|
+
Returns
|
|
550
|
+
-------
|
|
551
|
+
SecondOrderTensor
|
|
552
|
+
|
|
553
|
+
Examples
|
|
554
|
+
--------
|
|
555
|
+
>>> from Elasticipy.tensors.second_order import SecondOrderTensor
|
|
556
|
+
>>> A=SecondOrderTensor.rand(10)
|
|
557
|
+
>>> B=SecondOrderTensor.rand(10)
|
|
558
|
+
>>> AB_pair = A.dot(B)
|
|
559
|
+
>>> AB_pair.shape
|
|
560
|
+
(10,)
|
|
561
|
+
|
|
562
|
+
>>> AB_cross = A.dot(B, mode='cross')
|
|
563
|
+
>>> AB_cross.shape
|
|
564
|
+
(10, 10)
|
|
565
|
+
|
|
566
|
+
We can for instance check that:
|
|
567
|
+
|
|
568
|
+
>>> AB_pair[5] == A[5].dot(B[5])
|
|
569
|
+
True
|
|
570
|
+
|
|
571
|
+
and:
|
|
572
|
+
|
|
573
|
+
>>> AB_cross[0,1] == A[0].dot(B[1])
|
|
574
|
+
True
|
|
575
|
+
|
|
576
|
+
See Also
|
|
577
|
+
--------
|
|
578
|
+
ddot : Double-contraction product
|
|
579
|
+
"""
|
|
580
|
+
if self.shape == ():
|
|
581
|
+
ein_str = 'ik,...kj->...ij'
|
|
582
|
+
else:
|
|
583
|
+
if mode=='pair':
|
|
584
|
+
ein_str = '...ik,...kj->...ij'
|
|
585
|
+
elif mode=='cross':
|
|
586
|
+
ndim_0 = self.ndim
|
|
587
|
+
ndim_1 = other.ndim
|
|
588
|
+
indices_0 = ALPHABET[:ndim_0]
|
|
589
|
+
indices_1 = ALPHABET[:ndim_1].upper()
|
|
590
|
+
indices_2 = indices_0 + indices_1
|
|
591
|
+
ein_str = indices_0 + 'ik,' + indices_1 + 'kj->' + indices_2 + 'ij'
|
|
592
|
+
else:
|
|
593
|
+
raise ValueError('Invalid mode. Use "pair" or "cross".')
|
|
594
|
+
matrix = np.einsum(ein_str, self.matrix, other.matrix)
|
|
595
|
+
return SecondOrderTensor(matrix)
|
|
596
|
+
|
|
597
|
+
|
|
456
598
|
def matmul(self, other):
|
|
457
599
|
"""
|
|
458
600
|
Perform matrix-like product between tensor arrays. Each "product" is a matrix product between
|
|
@@ -481,28 +623,19 @@ class SecondOrderTensor:
|
|
|
481
623
|
--------
|
|
482
624
|
__mul__ : Element-wise matrix product
|
|
483
625
|
"""
|
|
626
|
+
warnings.warn(
|
|
627
|
+
'matmul() is deprecated and will be removed in a future version. Use dot(tensor,mode="cross") or '
|
|
628
|
+
'rotate(rotation,mode="cross") instead.',
|
|
629
|
+
DeprecationWarning,
|
|
630
|
+
stacklevel=2)
|
|
484
631
|
if isinstance(other, SecondOrderTensor):
|
|
485
|
-
|
|
632
|
+
return self.dot(other, mode='cross')
|
|
486
633
|
elif isinstance(other, Rotation) or is_orix_rotation(Rotation):
|
|
487
|
-
|
|
488
|
-
else:
|
|
489
|
-
other_matrix = other
|
|
490
|
-
matrix = self.matrix
|
|
491
|
-
shape_matrix = matrix.shape[:-2]
|
|
492
|
-
shape_other = other_matrix.shape[:-2]
|
|
493
|
-
extra_dim_matrix = len(shape_other)
|
|
494
|
-
extra_dim_other = len(shape_matrix)
|
|
495
|
-
matrix_expanded = matrix.reshape(shape_matrix + (1,) * extra_dim_other + (3, 3))
|
|
496
|
-
other_expanded = other_matrix.reshape((1,) * extra_dim_matrix + shape_other + (3, 3))
|
|
497
|
-
if isinstance(other, Rotation):
|
|
498
|
-
other_expanded_t = _transpose_matrix(other_expanded)
|
|
499
|
-
new_mat = np.matmul(np.matmul(other_expanded_t, matrix_expanded), other_expanded)
|
|
500
|
-
return self.__class__(np.squeeze(new_mat))
|
|
634
|
+
return self.rotate(other, mode='cross')
|
|
501
635
|
else:
|
|
502
|
-
|
|
503
|
-
return SecondOrderTensor(np.squeeze(new_mat))
|
|
636
|
+
raise ValueError('The input argument must be either a rotation or a SecondOrderTensor')
|
|
504
637
|
|
|
505
|
-
def
|
|
638
|
+
def transpose_array(self):
|
|
506
639
|
"""
|
|
507
640
|
Transpose the array of tensors
|
|
508
641
|
|
|
@@ -531,19 +664,19 @@ class SecondOrderTensor:
|
|
|
531
664
|
"""
|
|
532
665
|
Transpose the array of tensors.
|
|
533
666
|
|
|
534
|
-
It is actually an alias for
|
|
667
|
+
It is actually an alias for transpose_array()
|
|
535
668
|
|
|
536
669
|
Returns
|
|
537
670
|
-------
|
|
538
671
|
SecondOrderTensor
|
|
539
672
|
Transposed array
|
|
540
673
|
"""
|
|
541
|
-
return self.
|
|
674
|
+
return self.transpose_array()
|
|
542
675
|
|
|
543
|
-
def
|
|
676
|
+
def _transpose_tensor(self):
|
|
544
677
|
return _transpose_matrix(self.matrix)
|
|
545
678
|
|
|
546
|
-
def
|
|
679
|
+
def transpose_tensor(self):
|
|
547
680
|
"""
|
|
548
681
|
Transpose of tensors of the tensor array
|
|
549
682
|
|
|
@@ -554,11 +687,11 @@ class SecondOrderTensor:
|
|
|
554
687
|
|
|
555
688
|
See Also
|
|
556
689
|
--------
|
|
557
|
-
|
|
690
|
+
transpose_array : transpose the array (not the components)
|
|
558
691
|
"""
|
|
559
|
-
return self.__class__(self.
|
|
692
|
+
return self.__class__(self._transpose_tensor())
|
|
560
693
|
|
|
561
|
-
def ddot(self, other):
|
|
694
|
+
def ddot(self, other, mode='pair'):
|
|
562
695
|
"""
|
|
563
696
|
Double dot product (contraction of tensor product, usually denoted ":") of two tensors.
|
|
564
697
|
|
|
@@ -570,6 +703,11 @@ class SecondOrderTensor:
|
|
|
570
703
|
----------
|
|
571
704
|
other : SecondOrderTensor or np.ndarray
|
|
572
705
|
Tensor or tensor array to multiply by before contraction.
|
|
706
|
+
mode : str, optional
|
|
707
|
+
If "pair", the dot products are performed element-wise before contraction. Broadcasting rule applies.
|
|
708
|
+
If "cross", all the cross-combinations are computed, increasing the dimensionality.
|
|
709
|
+
If ``C=A.ddot(B, mode='cross')``, then ``C.shape = A.shape + B.shape``.
|
|
710
|
+
|
|
573
711
|
|
|
574
712
|
Returns
|
|
575
713
|
-------
|
|
@@ -578,10 +716,10 @@ class SecondOrderTensor:
|
|
|
578
716
|
|
|
579
717
|
See Also
|
|
580
718
|
--------
|
|
581
|
-
|
|
719
|
+
dot : contraction product ("dot product") between tensor.
|
|
582
720
|
|
|
583
721
|
"""
|
|
584
|
-
tensor_prod = self.
|
|
722
|
+
tensor_prod = self.transpose_tensor().dot(other, mode=mode)
|
|
585
723
|
return tensor_prod.trace()
|
|
586
724
|
|
|
587
725
|
def _flatten(self):
|
|
@@ -750,7 +888,7 @@ class SecondOrderTensor:
|
|
|
750
888
|
return self
|
|
751
889
|
|
|
752
890
|
def _symmetric_part(self):
|
|
753
|
-
return 0.5 * (self.matrix + self.
|
|
891
|
+
return 0.5 * (self.matrix + self._transpose_tensor())
|
|
754
892
|
|
|
755
893
|
def symmetric_part(self):
|
|
756
894
|
"""
|
|
@@ -776,7 +914,7 @@ class SecondOrderTensor:
|
|
|
776
914
|
SkewSymmetricSecondOrderTensor
|
|
777
915
|
Skew-symmetric tensor
|
|
778
916
|
"""
|
|
779
|
-
new_mat = 0.5 * (self.matrix - self.
|
|
917
|
+
new_mat = 0.5 * (self.matrix - self._transpose_tensor())
|
|
780
918
|
return SkewSymmetricSecondOrderTensor(new_mat)
|
|
781
919
|
|
|
782
920
|
def spherical_part(self):
|
|
@@ -922,7 +1060,7 @@ class SecondOrderTensor:
|
|
|
922
1060
|
|
|
923
1061
|
Parameters
|
|
924
1062
|
----------
|
|
925
|
-
shape : tuple, optional
|
|
1063
|
+
shape : int or tuple, optional
|
|
926
1064
|
Shape of the tensor array. If not provided, a single tensor is returned
|
|
927
1065
|
seed : int, optional
|
|
928
1066
|
Sets the seed for random generation. Useful to ensure reproducibility
|
|
@@ -940,7 +1078,7 @@ class SecondOrderTensor:
|
|
|
940
1078
|
--------
|
|
941
1079
|
Generate a single random tensor:
|
|
942
1080
|
|
|
943
|
-
>>> from Elasticipy.
|
|
1081
|
+
>>> from Elasticipy.tensors.second_order import SecondOrderTensor as tensor
|
|
944
1082
|
>>> tensor.rand(seed=123)
|
|
945
1083
|
Second-order tensor
|
|
946
1084
|
[[0.68235186 0.05382102 0.22035987]
|
|
@@ -954,6 +1092,8 @@ class SecondOrderTensor:
|
|
|
954
1092
|
"""
|
|
955
1093
|
if shape is None:
|
|
956
1094
|
shape = (3,3)
|
|
1095
|
+
elif isinstance(shape, int):
|
|
1096
|
+
shape = (shape, 3, 3)
|
|
957
1097
|
else:
|
|
958
1098
|
shape = shape + (3,3)
|
|
959
1099
|
rng = np.random.default_rng(seed)
|
|
@@ -1224,7 +1364,7 @@ class SecondOrderTensor:
|
|
|
1224
1364
|
flatten : Converts a tensor array to 1D tensor array
|
|
1225
1365
|
"""
|
|
1226
1366
|
try:
|
|
1227
|
-
from Elasticipy.
|
|
1367
|
+
from Elasticipy.tensors.stress_strain import StrainTensor, StressTensor
|
|
1228
1368
|
if isinstance(self, StrainTensor):
|
|
1229
1369
|
from pymatgen.analysis.elasticity import Strain as Constructor
|
|
1230
1370
|
elif isinstance(self, StressTensor):
|
|
@@ -1263,7 +1403,7 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1263
1403
|
--------
|
|
1264
1404
|
We can create a symmetric tensor by privoding the full matrix, as long it is symmetric:
|
|
1265
1405
|
|
|
1266
|
-
>>> from Elasticipy.
|
|
1406
|
+
>>> from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
|
|
1267
1407
|
>>> a = SymmetricSecondOrderTensor([[11, 12, 13],[12, 22, 23],[13, 23, 33]])
|
|
1268
1408
|
>>> print(a)
|
|
1269
1409
|
Symmetric second-order tensor
|
|
@@ -1281,7 +1421,10 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1281
1421
|
>>> a==b
|
|
1282
1422
|
True
|
|
1283
1423
|
"""
|
|
1284
|
-
|
|
1424
|
+
if isinstance(mat, SecondOrderTensor):
|
|
1425
|
+
mat = mat.matrix
|
|
1426
|
+
else:
|
|
1427
|
+
mat = np.asarray(mat, dtype=float)
|
|
1285
1428
|
mat_transposed = _transpose_matrix(mat)
|
|
1286
1429
|
if np.all(np.isclose(mat, mat_transposed)) or force_symmetry:
|
|
1287
1430
|
# The input matrix is symmetric
|
|
@@ -1308,35 +1451,79 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1308
1451
|
----------
|
|
1309
1452
|
array : np.ndarray or list
|
|
1310
1453
|
array to build the SymmetricSecondOrderTensor from. We must have array.ndim>0 and array.shape[-1]==6.
|
|
1454
|
+
|
|
1311
1455
|
Returns
|
|
1312
1456
|
-------
|
|
1313
1457
|
SymmetricSecondOrderTensor
|
|
1314
1458
|
|
|
1459
|
+
See Also
|
|
1460
|
+
--------
|
|
1461
|
+
from_Kelvin : Construct a tensor from vector(s) following the Kelvin notation
|
|
1462
|
+
|
|
1315
1463
|
Examples
|
|
1316
1464
|
--------
|
|
1317
|
-
>>> from Elasticipy.
|
|
1465
|
+
>>> from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
|
|
1318
1466
|
>>> SymmetricSecondOrderTensor.from_Voigt([11, 22, 33, 23, 13, 12])
|
|
1319
1467
|
Symmetric second-order tensor
|
|
1320
1468
|
[[11. 12. 13.]
|
|
1321
1469
|
[12. 22. 23.]
|
|
1322
1470
|
[13. 23. 33.]]
|
|
1471
|
+
"""
|
|
1472
|
+
matrix = _unmap(array, cls.voigt_map)
|
|
1473
|
+
return cls(matrix)
|
|
1323
1474
|
|
|
1475
|
+
def to_Voigt(self):
|
|
1324
1476
|
"""
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1477
|
+
Convert the tensor to vector, or slices of vector, following the Voigt convention.
|
|
1478
|
+
|
|
1479
|
+
If the tensor array has shape (m,n,...), the result will be of shape (m,n,...,6).
|
|
1480
|
+
|
|
1481
|
+
Returns
|
|
1482
|
+
-------
|
|
1483
|
+
numpy.ndarray
|
|
1484
|
+
Voigt vector summarizing the components
|
|
1485
|
+
"""
|
|
1486
|
+
return _map(self.matrix, self.voigt_map)
|
|
1487
|
+
|
|
1488
|
+
@classmethod
|
|
1489
|
+
def from_Kelvin(cls, array):
|
|
1490
|
+
"""
|
|
1491
|
+
Build a tensor from the Kelvin vector, or slices of Kelvin vectors
|
|
1492
|
+
|
|
1493
|
+
Parameters
|
|
1494
|
+
----------
|
|
1495
|
+
array : np.ndarray or list
|
|
1496
|
+
Vectors, or slices of vectors, consisting in components following the Kelvin convention
|
|
1497
|
+
Returns
|
|
1498
|
+
-------
|
|
1499
|
+
SymmetricSecondOrderTensor
|
|
1500
|
+
|
|
1501
|
+
See Also
|
|
1502
|
+
--------
|
|
1503
|
+
from_Voigt : construct a tensor from vector(s) following the Voigt notation
|
|
1504
|
+
to_Kelvin : convert the tensor to vector(s) following the Kelvin convention
|
|
1505
|
+
"""
|
|
1506
|
+
matrix = _unmap(array, kelvin_mapping)
|
|
1507
|
+
return cls(matrix)
|
|
1508
|
+
|
|
1509
|
+
def to_Kelvin(self):
|
|
1510
|
+
"""
|
|
1511
|
+
Convert the tensor to vector, or slices of vector, following the Kelvin(-Mandel) convention.
|
|
1512
|
+
|
|
1513
|
+
Returns
|
|
1514
|
+
-------
|
|
1515
|
+
numpy.ndarray
|
|
1516
|
+
|
|
1517
|
+
See Also
|
|
1518
|
+
--------
|
|
1519
|
+
from_Kelvin : Construct a tensor from vector(s) following the Kelvin convention
|
|
1520
|
+
to_Voigt : Convert the tensor to vector(s) following the Voigt convention
|
|
1521
|
+
"""
|
|
1522
|
+
return _map(self.matrix, kelvin_mapping)
|
|
1336
1523
|
|
|
1337
1524
|
def eig(self):
|
|
1338
1525
|
"""
|
|
1339
|
-
Compute the principal values (
|
|
1526
|
+
Compute the principal values (eigenvalues) and principal direction (eigenvectors) of the tensor, sorted in
|
|
1340
1527
|
descending order of principal values
|
|
1341
1528
|
|
|
1342
1529
|
Returns
|
|
@@ -1351,7 +1538,7 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1351
1538
|
eigvals : compute the principal values only
|
|
1352
1539
|
"""
|
|
1353
1540
|
eigvals, eigdir = np.linalg.eigh(self.matrix)
|
|
1354
|
-
return
|
|
1541
|
+
return eigvals[..., ::-1], eigdir[..., :, ::-1]
|
|
1355
1542
|
|
|
1356
1543
|
def eigvals(self):
|
|
1357
1544
|
"""
|
|
@@ -1386,7 +1573,7 @@ class SkewSymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1386
1573
|
--------
|
|
1387
1574
|
One can construct a skew-symmetric tensor by providing the full skew-symmetric matrix:
|
|
1388
1575
|
|
|
1389
|
-
>>> from Elasticipy.
|
|
1576
|
+
>>> from Elasticipy.tensors.second_order import SkewSymmetricSecondOrderTensor
|
|
1390
1577
|
>>> a = SkewSymmetricSecondOrderTensor([[0, 12, 13],[-12, 0, 23],[-13, -23, 0]])
|
|
1391
1578
|
>>> print(a)
|
|
1392
1579
|
Skew-symmetric second-order tensor
|
|
@@ -1437,6 +1624,8 @@ def rotation_to_matrix(rotation, return_transpose=False):
|
|
|
1437
1624
|
elif is_orix_rotation(rotation):
|
|
1438
1625
|
inv_rotation = ~rotation
|
|
1439
1626
|
matrix = inv_rotation.to_matrix()
|
|
1627
|
+
if matrix.shape == (1,3,3):
|
|
1628
|
+
matrix = matrix[0]
|
|
1440
1629
|
else:
|
|
1441
1630
|
raise TypeError('The input argument must be of class scipy.transform.Rotation or '
|
|
1442
1631
|
'orix.quaternion.rotation.Rotation')
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from Elasticipy.tensors.second_order import SymmetricSecondOrderTensor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StrainTensor(SymmetricSecondOrderTensor):
|
|
6
|
+
"""
|
|
7
|
+
Class for manipulating symmetric strain tensors or arrays of symmetric strain tensors.
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
name = 'Strain tensor'
|
|
11
|
+
voigt_map = [1, 1, 1, 2, 2, 2]
|
|
12
|
+
|
|
13
|
+
def principal_strains(self):
|
|
14
|
+
"""
|
|
15
|
+
Values of the principals strains.
|
|
16
|
+
|
|
17
|
+
If the tensor array is of shape [m,n,...], the results will be of shape [m,n,...,3].
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
np.ndarray
|
|
22
|
+
Principal strain values
|
|
23
|
+
"""
|
|
24
|
+
return self.eigvals()
|
|
25
|
+
|
|
26
|
+
def volumetric_strain(self):
|
|
27
|
+
"""
|
|
28
|
+
Volumetric change (1st invariant of the strain tensor)
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
numpy.ndarray or float
|
|
33
|
+
Volumetric change
|
|
34
|
+
"""
|
|
35
|
+
return self.I1
|
|
36
|
+
|
|
37
|
+
def eq_strain(self):
|
|
38
|
+
"""von Mises equivalent strain"""
|
|
39
|
+
return np.sqrt(2/3 * self.ddot(self))
|
|
40
|
+
|
|
41
|
+
def elastic_energy(self, stress):
|
|
42
|
+
"""
|
|
43
|
+
Compute the elastic energy.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
stress : StressTensor
|
|
48
|
+
Corresponding stress tensor
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
Volumetric elastic energy
|
|
53
|
+
"""
|
|
54
|
+
return 0.5 * self.ddot(stress)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class StressTensor(SymmetricSecondOrderTensor):
|
|
58
|
+
"""
|
|
59
|
+
Class for manipulating stress tensors or arrays of stress tensors.
|
|
60
|
+
"""
|
|
61
|
+
name = 'Stress tensor'
|
|
62
|
+
|
|
63
|
+
def principal_stresses(self):
|
|
64
|
+
"""
|
|
65
|
+
Values of the principals stresses.
|
|
66
|
+
|
|
67
|
+
If the tensor array is of shape [m,n,...], the results will be of shape [m,n,...,3].
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
np.ndarray
|
|
72
|
+
Principal stresses
|
|
73
|
+
"""
|
|
74
|
+
return self.eigvals()
|
|
75
|
+
|
|
76
|
+
def vonMises(self):
|
|
77
|
+
"""
|
|
78
|
+
von Mises equivalent stress.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
np.ndarray or float
|
|
83
|
+
von Mises equivalent stress
|
|
84
|
+
|
|
85
|
+
See Also
|
|
86
|
+
--------
|
|
87
|
+
Tresca : Tresca equivalent stress
|
|
88
|
+
"""
|
|
89
|
+
return np.sqrt(3 * self.J2)
|
|
90
|
+
|
|
91
|
+
def Tresca(self):
|
|
92
|
+
"""
|
|
93
|
+
Tresca(-Guest) equivalent stress.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
np.ndarray or float
|
|
98
|
+
Tresca equivalent stress
|
|
99
|
+
|
|
100
|
+
See Also
|
|
101
|
+
--------
|
|
102
|
+
vonMises : von Mises equivalent stress
|
|
103
|
+
"""
|
|
104
|
+
ps = self.principal_stresses()
|
|
105
|
+
return ps[...,0] - ps[...,-1]
|
|
106
|
+
|
|
107
|
+
def hydrostatic_pressure(self):
|
|
108
|
+
"""
|
|
109
|
+
Hydrostatic pressure
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
np.ndarray or float
|
|
114
|
+
|
|
115
|
+
See Also
|
|
116
|
+
--------
|
|
117
|
+
sphericalPart : spherical part of the stress
|
|
118
|
+
"""
|
|
119
|
+
return -self.I1/3
|
|
120
|
+
|
|
121
|
+
def elastic_energy(self, strain, mode='pair'):
|
|
122
|
+
"""
|
|
123
|
+
Compute the elastic energy.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
strain : StrainTensor
|
|
128
|
+
Corresponding elastic strain tensor
|
|
129
|
+
mode : str, optional
|
|
130
|
+
If 'pair' (default), the elastic energies are computed element-wise. Broadcasting rule applies.
|
|
131
|
+
If 'cross', each cross-combination of stress and strain are considered.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
numpy.ndarray
|
|
136
|
+
Volumetric elastic energy
|
|
137
|
+
"""
|
|
138
|
+
return 0.5 * self.ddot(strain, mode=mode)
|