elasticipy 2.8.10__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Elasticipy/FourthOrderTensor.py +360 -44
- Elasticipy/Plasticity.py +222 -15
- Elasticipy/SecondOrderTensor.py +338 -66
- Elasticipy/StressStrainTensors.py +13 -8
- Elasticipy/ThermalExpansion.py +88 -17
- {elasticipy-2.8.10.dist-info → elasticipy-3.0.0.dist-info}/METADATA +2 -1
- elasticipy-3.0.0.dist-info/RECORD +15 -0
- {elasticipy-2.8.10.dist-info → elasticipy-3.0.0.dist-info}/WHEEL +1 -1
- elasticipy-2.8.10.dist-info/RECORD +0 -15
- {elasticipy-2.8.10.dist-info → elasticipy-3.0.0.dist-info}/LICENSE +0 -0
- {elasticipy-2.8.10.dist-info → elasticipy-3.0.0.dist-info}/top_level.txt +0 -0
Elasticipy/SecondOrderTensor.py
CHANGED
|
@@ -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
|
|
@@ -290,7 +331,7 @@ class SecondOrderTensor:
|
|
|
290
331
|
@property
|
|
291
332
|
def J2(self):
|
|
292
333
|
"""
|
|
293
|
-
Second invariant of the deviatoric part of the
|
|
334
|
+
Second invariant of the deviatoric part of the tensor.
|
|
294
335
|
|
|
295
336
|
Returns
|
|
296
337
|
-------
|
|
@@ -302,7 +343,7 @@ class SecondOrderTensor:
|
|
|
302
343
|
@property
|
|
303
344
|
def J3(self):
|
|
304
345
|
"""
|
|
305
|
-
Third invariant of the deviatoric part of the
|
|
346
|
+
Third invariant of the deviatoric part of the tensor.
|
|
306
347
|
|
|
307
348
|
Returns
|
|
308
349
|
-------
|
|
@@ -311,6 +352,48 @@ class SecondOrderTensor:
|
|
|
311
352
|
"""
|
|
312
353
|
return self.deviatoric_part().I3
|
|
313
354
|
|
|
355
|
+
def Lode_angle(self, degrees=False):
|
|
356
|
+
"""
|
|
357
|
+
Computes the Lode angle of the tensor.
|
|
358
|
+
|
|
359
|
+
The returned value is defined from the positive cosine (see Notes).
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
degrees : bool, optional
|
|
364
|
+
Whether to return the angle in degrees or not
|
|
365
|
+
|
|
366
|
+
Returns
|
|
367
|
+
-------
|
|
368
|
+
float or numpy.ndarray
|
|
369
|
+
|
|
370
|
+
See Also
|
|
371
|
+
--------
|
|
372
|
+
J2 : Second invariant of the deviatoric part
|
|
373
|
+
J3 : Third invariant of the deviatoric part
|
|
374
|
+
|
|
375
|
+
Notes
|
|
376
|
+
-----
|
|
377
|
+
The Lode angle is defined such that:
|
|
378
|
+
|
|
379
|
+
.. math::
|
|
380
|
+
|
|
381
|
+
\\cos(3\\theta)= \\frac{J_3}{2}\\left(\\frac{3}{J_2}\\right)^{3/2}
|
|
382
|
+
"""
|
|
383
|
+
J2 = np.atleast_1d(self.J2)
|
|
384
|
+
J3 = np.atleast_1d(self.J3)
|
|
385
|
+
non_hydro = J2 !=0.
|
|
386
|
+
cosine = np.ones(shape=J3.shape) * np.nan
|
|
387
|
+
cosine[non_hydro] = J3[non_hydro] / 2 * (3 / J2[non_hydro] )**(3 / 2)
|
|
388
|
+
if degrees:
|
|
389
|
+
theta = np.arccos(cosine) * 60 / np.pi
|
|
390
|
+
else:
|
|
391
|
+
theta = np.arccos(cosine) / 3
|
|
392
|
+
if self.shape:
|
|
393
|
+
return theta
|
|
394
|
+
else:
|
|
395
|
+
return theta[0]
|
|
396
|
+
|
|
314
397
|
def trace(self):
|
|
315
398
|
"""
|
|
316
399
|
Return the traces of the tensor array
|
|
@@ -349,13 +432,9 @@ class SecondOrderTensor:
|
|
|
349
432
|
matmul : matrix-like multiplication of tensor arrays
|
|
350
433
|
"""
|
|
351
434
|
if isinstance(B, SecondOrderTensor):
|
|
352
|
-
|
|
353
|
-
return SecondOrderTensor(new_mat)
|
|
435
|
+
return self.dot(B, mode='pair')
|
|
354
436
|
elif isinstance(B, Rotation) or is_orix_rotation(B):
|
|
355
|
-
|
|
356
|
-
new_matrix = np.matmul(np.matmul(transpose_matrices, self.matrix), rotation_matrices)
|
|
357
|
-
# In case of rotation, the property of the transformed tensor is kept
|
|
358
|
-
return self.__class__(new_matrix)
|
|
437
|
+
return self.rotate(B, mode='pair')
|
|
359
438
|
elif isinstance(B, (float, int)):
|
|
360
439
|
return self.__class__(self.matrix * B)
|
|
361
440
|
elif isinstance(B, np.ndarray):
|
|
@@ -371,13 +450,63 @@ class SecondOrderTensor:
|
|
|
371
450
|
else:
|
|
372
451
|
raise ValueError('The input argument must be a tensor, an ndarray, a rotation or a scalar value.')
|
|
373
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
|
+
|
|
374
491
|
def __rmul__(self, other):
|
|
375
492
|
if isinstance(other, (float, int)):
|
|
376
493
|
return self.__mul__(other)
|
|
377
494
|
else:
|
|
378
495
|
raise NotImplementedError('Left multiplication is only implemented for scalar values.')
|
|
379
496
|
|
|
380
|
-
def
|
|
497
|
+
def __truediv__(self, other):
|
|
498
|
+
new_mat = np.zeros(self.matrix.shape)
|
|
499
|
+
non_zero = np.any(self.matrix, axis=(-1, -2))
|
|
500
|
+
if isinstance(other, (float, int)):
|
|
501
|
+
new_mat[non_zero] = self.matrix[non_zero] / other # Hack to force 0/0 = 0
|
|
502
|
+
elif isinstance(other, np.ndarray) and (self.shape == other.shape):
|
|
503
|
+
new_mat[non_zero] = np.einsum('pij,p->pij', self.matrix[non_zero], 1/other[non_zero])
|
|
504
|
+
return self.__class__(new_mat)
|
|
505
|
+
else:
|
|
506
|
+
raise NotImplementedError('Tensors can only be divided by scalar values or by arrays of the same shape.')
|
|
507
|
+
return self.__class__(new_mat)
|
|
508
|
+
|
|
509
|
+
def __eq__(self, other):
|
|
381
510
|
"""
|
|
382
511
|
Check whether the tensors in the tensor array are equal
|
|
383
512
|
|
|
@@ -388,7 +517,7 @@ class SecondOrderTensor:
|
|
|
388
517
|
|
|
389
518
|
Returns
|
|
390
519
|
-------
|
|
391
|
-
|
|
520
|
+
numpy.ndarray
|
|
392
521
|
True element is True if the corresponding tensors are equal.
|
|
393
522
|
"""
|
|
394
523
|
if isinstance(other, SecondOrderTensor):
|
|
@@ -399,6 +528,73 @@ class SecondOrderTensor:
|
|
|
399
528
|
else:
|
|
400
529
|
raise ValueError('The value to compare must be an array of shape {} or {}'.format(self.shape, self.shape + (3,3)))
|
|
401
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.SecondOrderTensor 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
|
+
|
|
402
598
|
def matmul(self, other):
|
|
403
599
|
"""
|
|
404
600
|
Perform matrix-like product between tensor arrays. Each "product" is a matrix product between
|
|
@@ -427,28 +623,19 @@ class SecondOrderTensor:
|
|
|
427
623
|
--------
|
|
428
624
|
__mul__ : Element-wise matrix product
|
|
429
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)
|
|
430
631
|
if isinstance(other, SecondOrderTensor):
|
|
431
|
-
|
|
632
|
+
return self.dot(other, mode='cross')
|
|
432
633
|
elif isinstance(other, Rotation) or is_orix_rotation(Rotation):
|
|
433
|
-
|
|
434
|
-
else:
|
|
435
|
-
other_matrix = other
|
|
436
|
-
matrix = self.matrix
|
|
437
|
-
shape_matrix = matrix.shape[:-2]
|
|
438
|
-
shape_other = other_matrix.shape[:-2]
|
|
439
|
-
extra_dim_matrix = len(shape_other)
|
|
440
|
-
extra_dim_other = len(shape_matrix)
|
|
441
|
-
matrix_expanded = matrix.reshape(shape_matrix + (1,) * extra_dim_other + (3, 3))
|
|
442
|
-
other_expanded = other_matrix.reshape((1,) * extra_dim_matrix + shape_other + (3, 3))
|
|
443
|
-
if isinstance(other, Rotation):
|
|
444
|
-
other_expanded_t = _transpose_matrix(other_expanded)
|
|
445
|
-
new_mat = np.matmul(np.matmul(other_expanded_t, matrix_expanded), other_expanded)
|
|
446
|
-
return self.__class__(np.squeeze(new_mat))
|
|
634
|
+
return self.rotate(other, mode='cross')
|
|
447
635
|
else:
|
|
448
|
-
|
|
449
|
-
return SecondOrderTensor(np.squeeze(new_mat))
|
|
636
|
+
raise ValueError('The input argument must be either a rotation or a SecondOrderTensor')
|
|
450
637
|
|
|
451
|
-
def
|
|
638
|
+
def transpose_array(self):
|
|
452
639
|
"""
|
|
453
640
|
Transpose the array of tensors
|
|
454
641
|
|
|
@@ -477,19 +664,19 @@ class SecondOrderTensor:
|
|
|
477
664
|
"""
|
|
478
665
|
Transpose the array of tensors.
|
|
479
666
|
|
|
480
|
-
It is actually an alias for
|
|
667
|
+
It is actually an alias for transpose_array()
|
|
481
668
|
|
|
482
669
|
Returns
|
|
483
670
|
-------
|
|
484
671
|
SecondOrderTensor
|
|
485
672
|
Transposed array
|
|
486
673
|
"""
|
|
487
|
-
return self.
|
|
674
|
+
return self.transpose_array()
|
|
488
675
|
|
|
489
|
-
def
|
|
676
|
+
def _transpose_tensor(self):
|
|
490
677
|
return _transpose_matrix(self.matrix)
|
|
491
678
|
|
|
492
|
-
def
|
|
679
|
+
def transpose_tensor(self):
|
|
493
680
|
"""
|
|
494
681
|
Transpose of tensors of the tensor array
|
|
495
682
|
|
|
@@ -500,11 +687,11 @@ class SecondOrderTensor:
|
|
|
500
687
|
|
|
501
688
|
See Also
|
|
502
689
|
--------
|
|
503
|
-
|
|
690
|
+
transpose_array : transpose the array (not the components)
|
|
504
691
|
"""
|
|
505
|
-
return self.__class__(self.
|
|
692
|
+
return self.__class__(self._transpose_tensor())
|
|
506
693
|
|
|
507
|
-
def ddot(self, other):
|
|
694
|
+
def ddot(self, other, mode='pair'):
|
|
508
695
|
"""
|
|
509
696
|
Double dot product (contraction of tensor product, usually denoted ":") of two tensors.
|
|
510
697
|
|
|
@@ -516,6 +703,11 @@ class SecondOrderTensor:
|
|
|
516
703
|
----------
|
|
517
704
|
other : SecondOrderTensor or np.ndarray
|
|
518
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
|
+
|
|
519
711
|
|
|
520
712
|
Returns
|
|
521
713
|
-------
|
|
@@ -524,10 +716,10 @@ class SecondOrderTensor:
|
|
|
524
716
|
|
|
525
717
|
See Also
|
|
526
718
|
--------
|
|
527
|
-
|
|
719
|
+
dot : contraction product ("dot product") between tensor.
|
|
528
720
|
|
|
529
721
|
"""
|
|
530
|
-
tensor_prod = self.
|
|
722
|
+
tensor_prod = self.transpose_tensor().dot(other, mode=mode)
|
|
531
723
|
return tensor_prod.trace()
|
|
532
724
|
|
|
533
725
|
def _flatten(self):
|
|
@@ -696,7 +888,7 @@ class SecondOrderTensor:
|
|
|
696
888
|
return self
|
|
697
889
|
|
|
698
890
|
def _symmetric_part(self):
|
|
699
|
-
return 0.5 * (self.matrix + self.
|
|
891
|
+
return 0.5 * (self.matrix + self._transpose_tensor())
|
|
700
892
|
|
|
701
893
|
def symmetric_part(self):
|
|
702
894
|
"""
|
|
@@ -719,10 +911,10 @@ class SecondOrderTensor:
|
|
|
719
911
|
|
|
720
912
|
Returns
|
|
721
913
|
-------
|
|
722
|
-
|
|
914
|
+
SkewSymmetricSecondOrderTensor
|
|
723
915
|
Skew-symmetric tensor
|
|
724
916
|
"""
|
|
725
|
-
new_mat = 0.5 * (self.matrix - self.
|
|
917
|
+
new_mat = 0.5 * (self.matrix - self._transpose_tensor())
|
|
726
918
|
return SkewSymmetricSecondOrderTensor(new_mat)
|
|
727
919
|
|
|
728
920
|
def spherical_part(self):
|
|
@@ -731,7 +923,7 @@ class SecondOrderTensor:
|
|
|
731
923
|
|
|
732
924
|
Returns
|
|
733
925
|
-------
|
|
734
|
-
|
|
926
|
+
self
|
|
735
927
|
Spherical part
|
|
736
928
|
|
|
737
929
|
See Also
|
|
@@ -748,7 +940,7 @@ class SecondOrderTensor:
|
|
|
748
940
|
|
|
749
941
|
Returns
|
|
750
942
|
-------
|
|
751
|
-
|
|
943
|
+
self
|
|
752
944
|
|
|
753
945
|
See Also
|
|
754
946
|
--------
|
|
@@ -769,7 +961,7 @@ class SecondOrderTensor:
|
|
|
769
961
|
|
|
770
962
|
Returns
|
|
771
963
|
-------
|
|
772
|
-
|
|
964
|
+
cls
|
|
773
965
|
Array of identity tensors
|
|
774
966
|
|
|
775
967
|
See Also
|
|
@@ -798,7 +990,7 @@ class SecondOrderTensor:
|
|
|
798
990
|
|
|
799
991
|
Returns
|
|
800
992
|
-------
|
|
801
|
-
|
|
993
|
+
cls
|
|
802
994
|
Array of ones tensors
|
|
803
995
|
|
|
804
996
|
See Also
|
|
@@ -826,7 +1018,7 @@ class SecondOrderTensor:
|
|
|
826
1018
|
|
|
827
1019
|
Returns
|
|
828
1020
|
-------
|
|
829
|
-
|
|
1021
|
+
cls
|
|
830
1022
|
Array of ones tensors
|
|
831
1023
|
|
|
832
1024
|
See Also
|
|
@@ -855,7 +1047,7 @@ class SecondOrderTensor:
|
|
|
855
1047
|
will be of the same shape as magnitude.
|
|
856
1048
|
Returns
|
|
857
1049
|
-------
|
|
858
|
-
|
|
1050
|
+
cls
|
|
859
1051
|
tensor or tensor array
|
|
860
1052
|
"""
|
|
861
1053
|
mat = _tensor_from_direction_magnitude(u, u, magnitude)
|
|
@@ -868,14 +1060,14 @@ class SecondOrderTensor:
|
|
|
868
1060
|
|
|
869
1061
|
Parameters
|
|
870
1062
|
----------
|
|
871
|
-
shape : tuple, optional
|
|
1063
|
+
shape : int or tuple, optional
|
|
872
1064
|
Shape of the tensor array. If not provided, a single tensor is returned
|
|
873
1065
|
seed : int, optional
|
|
874
1066
|
Sets the seed for random generation. Useful to ensure reproducibility
|
|
875
1067
|
|
|
876
1068
|
Returns
|
|
877
1069
|
-------
|
|
878
|
-
|
|
1070
|
+
cls
|
|
879
1071
|
Tensor or tensor array of uniform random value
|
|
880
1072
|
|
|
881
1073
|
See Also
|
|
@@ -900,6 +1092,8 @@ class SecondOrderTensor:
|
|
|
900
1092
|
"""
|
|
901
1093
|
if shape is None:
|
|
902
1094
|
shape = (3,3)
|
|
1095
|
+
elif isinstance(shape, int):
|
|
1096
|
+
shape = (shape, 3, 3)
|
|
903
1097
|
else:
|
|
904
1098
|
shape = shape + (3,3)
|
|
905
1099
|
rng = np.random.default_rng(seed)
|
|
@@ -930,7 +1124,7 @@ class SecondOrderTensor:
|
|
|
930
1124
|
|
|
931
1125
|
Returns
|
|
932
1126
|
-------
|
|
933
|
-
|
|
1127
|
+
cls
|
|
934
1128
|
Tensor or tensor array of normal random value
|
|
935
1129
|
"""
|
|
936
1130
|
if shape is None:
|
|
@@ -964,7 +1158,7 @@ class SecondOrderTensor:
|
|
|
964
1158
|
will be of the same shape as magnitude.
|
|
965
1159
|
Returns
|
|
966
1160
|
-------
|
|
967
|
-
|
|
1161
|
+
cls
|
|
968
1162
|
tensor or tensor array
|
|
969
1163
|
"""
|
|
970
1164
|
if np.abs(np.dot(u, v)) > 1e-5:
|
|
@@ -1227,7 +1421,10 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1227
1421
|
>>> a==b
|
|
1228
1422
|
True
|
|
1229
1423
|
"""
|
|
1230
|
-
|
|
1424
|
+
if isinstance(mat, SecondOrderTensor):
|
|
1425
|
+
mat = mat.matrix
|
|
1426
|
+
else:
|
|
1427
|
+
mat = np.asarray(mat, dtype=float)
|
|
1231
1428
|
mat_transposed = _transpose_matrix(mat)
|
|
1232
1429
|
if np.all(np.isclose(mat, mat_transposed)) or force_symmetry:
|
|
1233
1430
|
# The input matrix is symmetric
|
|
@@ -1254,10 +1451,15 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1254
1451
|
----------
|
|
1255
1452
|
array : np.ndarray or list
|
|
1256
1453
|
array to build the SymmetricSecondOrderTensor from. We must have array.ndim>0 and array.shape[-1]==6.
|
|
1454
|
+
|
|
1257
1455
|
Returns
|
|
1258
1456
|
-------
|
|
1259
1457
|
SymmetricSecondOrderTensor
|
|
1260
1458
|
|
|
1459
|
+
See Also
|
|
1460
|
+
--------
|
|
1461
|
+
from_Kelvin : Construct a tensor from vector(s) following the Kelvin notation
|
|
1462
|
+
|
|
1261
1463
|
Examples
|
|
1262
1464
|
--------
|
|
1263
1465
|
>>> from Elasticipy.SecondOrderTensor import SymmetricSecondOrderTensor
|
|
@@ -1266,25 +1468,93 @@ class SymmetricSecondOrderTensor(SecondOrderTensor):
|
|
|
1266
1468
|
[[11. 12. 13.]
|
|
1267
1469
|
[12. 22. 23.]
|
|
1268
1470
|
[13. 23. 33.]]
|
|
1471
|
+
"""
|
|
1472
|
+
matrix = _unmap(array, cls.voigt_map)
|
|
1473
|
+
return cls(matrix)
|
|
1269
1474
|
|
|
1475
|
+
def to_Voigt(self):
|
|
1270
1476
|
"""
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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)
|
|
1282
1523
|
|
|
1283
1524
|
def eig(self):
|
|
1284
|
-
|
|
1525
|
+
"""
|
|
1526
|
+
Compute the principal values (eigenvalues) and principal direction (eigenvectors) of the tensor, sorted in
|
|
1527
|
+
descending order of principal values
|
|
1528
|
+
|
|
1529
|
+
Returns
|
|
1530
|
+
-------
|
|
1531
|
+
numpy.ndarray
|
|
1532
|
+
Principal values
|
|
1533
|
+
numpy.ndarray
|
|
1534
|
+
Principal directions
|
|
1535
|
+
|
|
1536
|
+
See Also
|
|
1537
|
+
--------
|
|
1538
|
+
eigvals : compute the principal values only
|
|
1539
|
+
"""
|
|
1540
|
+
eigvals, eigdir = np.linalg.eigh(self.matrix)
|
|
1541
|
+
return eigvals[..., ::-1], eigdir[..., :, ::-1]
|
|
1285
1542
|
|
|
1286
1543
|
def eigvals(self):
|
|
1287
|
-
|
|
1544
|
+
"""
|
|
1545
|
+
Compute the principal values (eigenvalues), sorted in descending order.
|
|
1546
|
+
|
|
1547
|
+
Returns
|
|
1548
|
+
-------
|
|
1549
|
+
numpy.ndarray
|
|
1550
|
+
Principal values
|
|
1551
|
+
|
|
1552
|
+
See Also
|
|
1553
|
+
--------
|
|
1554
|
+
eig : return the principal values and principal directions
|
|
1555
|
+
"""
|
|
1556
|
+
eigvals = np.linalg.eigvalsh(self.matrix)
|
|
1557
|
+
return np.flip(eigvals,axis=-1)
|
|
1288
1558
|
|
|
1289
1559
|
|
|
1290
1560
|
class SkewSymmetricSecondOrderTensor(SecondOrderTensor):
|
|
@@ -1354,6 +1624,8 @@ def rotation_to_matrix(rotation, return_transpose=False):
|
|
|
1354
1624
|
elif is_orix_rotation(rotation):
|
|
1355
1625
|
inv_rotation = ~rotation
|
|
1356
1626
|
matrix = inv_rotation.to_matrix()
|
|
1627
|
+
if matrix.shape == (1,3,3):
|
|
1628
|
+
matrix = matrix[0]
|
|
1357
1629
|
else:
|
|
1358
1630
|
raise TypeError('The input argument must be of class scipy.transform.Rotation or '
|
|
1359
1631
|
'orix.quaternion.rotation.Rotation')
|