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.
@@ -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
@@ -391,13 +432,9 @@ class SecondOrderTensor:
391
432
  matmul : matrix-like multiplication of tensor arrays
392
433
  """
393
434
  if isinstance(B, SecondOrderTensor):
394
- new_mat = np.matmul(self.matrix, B.matrix)
395
- return SecondOrderTensor(new_mat)
435
+ return self.dot(B, mode='pair')
396
436
  elif isinstance(B, Rotation) or is_orix_rotation(B):
397
- rotation_matrices, transpose_matrices = rotation_to_matrix(B, return_transpose=True)
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) -> np.ndarray:
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
- np.array of bool
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
- other_matrix = other.matrix
632
+ return self.dot(other, mode='cross')
486
633
  elif isinstance(other, Rotation) or is_orix_rotation(Rotation):
487
- other_matrix = rotation_to_matrix(other)
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
- new_mat = np.matmul(matrix_expanded, other_expanded)
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 transposeArray(self):
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 transposeArray()
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.transposeArray()
674
+ return self.transpose_array()
542
675
 
543
- def _transposeTensor(self):
676
+ def _transpose_tensor(self):
544
677
  return _transpose_matrix(self.matrix)
545
678
 
546
- def transposeTensor(self):
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
- Transpose : transpose the array (not the components)
690
+ transpose_array : transpose the array (not the components)
558
691
  """
559
- return self.__class__(self._transposeTensor())
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
- matmul : matrix-like product between two tensor arrays.
719
+ dot : contraction product ("dot product") between tensor.
582
720
 
583
721
  """
584
- tensor_prod = self.transposeTensor()*other
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._transposeTensor())
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._transposeTensor())
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.SecondOrderTensor import SecondOrderTensor as tensor
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.StressStrainTensors import StrainTensor, StressTensor
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.SecondOrderTensor import SymmetricSecondOrderTensor
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
- mat = np.asarray(mat, dtype=float)
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.SecondOrderTensor import SymmetricSecondOrderTensor
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
- array = np.asarray(array)
1326
- shape = array.shape
1327
- if shape and (shape[-1] == 6):
1328
- new_shape = shape[:-1] + (3, 3)
1329
- unvoigted_matrix = np.zeros(new_shape)
1330
- voigt = [[0, 0], [1, 1], [2, 2], [1, 2], [0, 2], [0, 1]]
1331
- for i in range(6):
1332
- unvoigted_matrix[..., voigt[i][0], voigt[i][1]] = array[..., i] / cls.voigt_map[i]
1333
- return cls(unvoigted_matrix)
1334
- else:
1335
- 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)
1336
1523
 
1337
1524
  def eig(self):
1338
1525
  """
1339
- Compute the principal values (eigenvaleues) and principal direction (eigenvectors) of the tensor, sorted in
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 np.flip(eigvals,axis=-1), np.flip(eigdir,axis=-1)
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.SecondOrderTensor import SkewSymmetricSecondOrderTensor
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)