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.
@@ -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._transposeTensor()).trace(axis1=-1, axis2=-2)
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 stress tensor.
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 stress tensor.
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
- new_mat = np.matmul(self.matrix, B.matrix)
353
- return SecondOrderTensor(new_mat)
435
+ return self.dot(B, mode='pair')
354
436
  elif isinstance(B, Rotation) or is_orix_rotation(B):
355
- rotation_matrices, transpose_matrices = rotation_to_matrix(B, return_transpose=True)
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 __eq__(self, other) -> np.ndarray:
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
- np.array of bool
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
- other_matrix = other.matrix
632
+ return self.dot(other, mode='cross')
432
633
  elif isinstance(other, Rotation) or is_orix_rotation(Rotation):
433
- other_matrix = rotation_to_matrix(other)
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
- new_mat = np.matmul(matrix_expanded, other_expanded)
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 transposeArray(self):
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 transposeArray()
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.transposeArray()
674
+ return self.transpose_array()
488
675
 
489
- def _transposeTensor(self):
676
+ def _transpose_tensor(self):
490
677
  return _transpose_matrix(self.matrix)
491
678
 
492
- def transposeTensor(self):
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
- Transpose : transpose the array (not the components)
690
+ transpose_array : transpose the array (not the components)
504
691
  """
505
- return self.__class__(self._transposeTensor())
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
- matmul : matrix-like product between two tensor arrays.
719
+ dot : contraction product ("dot product") between tensor.
528
720
 
529
721
  """
530
- tensor_prod = self.transposeTensor()*other
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._transposeTensor())
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
- SecondOrderTensor
914
+ SkewSymmetricSecondOrderTensor
723
915
  Skew-symmetric tensor
724
916
  """
725
- new_mat = 0.5 * (self.matrix - self._transposeTensor())
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
- SecondOrderTensor
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
- SecondOrderTensor
943
+ self
752
944
 
753
945
  See Also
754
946
  --------
@@ -769,7 +961,7 @@ class SecondOrderTensor:
769
961
 
770
962
  Returns
771
963
  -------
772
- SecondOrderTensor
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
- SecondOrderTensor
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
- SecondOrderTensor
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
- SecondOrderTensor
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
- SecondOrderTensor
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
- SecondOrderTensor
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
- SecondOrderTensor
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
- mat = np.asarray(mat, dtype=float)
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
- array = np.asarray(array)
1272
- shape = array.shape
1273
- if shape and (shape[-1] == 6):
1274
- new_shape = shape[:-1] + (3, 3)
1275
- unvoigted_matrix = np.zeros(new_shape)
1276
- voigt = [[0, 0], [1, 1], [2, 2], [1, 2], [0, 2], [0, 1]]
1277
- for i in range(6):
1278
- unvoigted_matrix[..., voigt[i][0], voigt[i][1]] = array[..., i] / cls.voigt_map[i]
1279
- return cls(unvoigted_matrix)
1280
- else:
1281
- raise ValueError("array must be of shape (6,) or (...,6) with Voigt vector")
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
- return np.linalg.eigh(self.matrix)
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
- return np.linalg.eigvalsh(self.matrix)
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')