elasticipy 6.0.0__py3-none-any.whl → 6.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
elasticipy/gui/about.py CHANGED
@@ -1,25 +1,27 @@
1
1
  from qtpy.QtWidgets import QVBoxLayout, QLabel, QPushButton
2
2
  from qtpy.QtGui import QPixmap
3
3
  from qtpy.QtCore import Qt
4
+ from importlib import resources
4
5
 
5
- def about(dialog, logo_path):
6
- dialog.setWindowTitle("About elasticipy")
6
+ def about(dialog):
7
+ dialog.setWindowTitle("About Elasticipy")
7
8
  dialog.setFixedWidth(400)
8
9
 
9
10
  layout = QVBoxLayout(dialog)
10
11
 
11
- if logo_path.exists():
12
- logo = QLabel()
13
- pixmap = QPixmap(str(logo_path))
14
- pixmap = pixmap.scaledToWidth(250, Qt.SmoothTransformation)
15
- logo.setPixmap(pixmap)
16
- logo.setAlignment(Qt.AlignCenter)
17
- layout.addWidget(logo)
12
+ logo = QLabel()
13
+ pixmap = QPixmap(
14
+ str(resources.files("elasticipy.resources") / "logo_text.png")
15
+ )
16
+ pixmap = pixmap.scaledToWidth(250, Qt.SmoothTransformation)
17
+ logo.setPixmap(pixmap)
18
+ logo.setAlignment(Qt.AlignCenter)
19
+ layout.addWidget(logo)
18
20
 
19
21
  # --- Text ---
20
22
  text = QLabel(
21
23
  "A Python library for elasticity tensors computations<br><br>"
22
- 20242025 Dorian Depriester, MIT Licence"
24
+ 20252026 Dorian Depriester, MIT Licence"
23
25
  )
24
26
  text.setAlignment(Qt.AlignCenter)
25
27
  layout.addWidget(text)
elasticipy/gui/gui.py CHANGED
@@ -11,16 +11,12 @@ from elasticipy.crystal_symmetries import SYMMETRIES
11
11
  from elasticipy.gui.rotate_window import EulerBungeDialog
12
12
  from elasticipy.gui.about import about
13
13
  from elasticipy.tensors.elasticity import StiffnessTensor
14
- from pathlib import Path
14
+ from importlib import resources
15
15
 
16
16
  from scipy.spatial.transform import Rotation
17
17
 
18
18
  WHICH_OPTIONS = {'Mean': 'mean', 'Max': 'max', 'Min': 'min', 'Std. dev.': 'std'}
19
19
 
20
- # --- Logo ---
21
- here = Path(__file__).resolve().parent
22
- LOGO_PATH = here / ".." / "resources" / "logo_text.svg"
23
- ICON_PATH = here / ".." / "resources" / "favicon.png"
24
20
 
25
21
  class ElasticityGUI(QMainWindow):
26
22
  def __init__(self):
@@ -388,7 +384,7 @@ class ElasticityGUI(QMainWindow):
388
384
 
389
385
  def show_about(self):
390
386
  dialog = QDialog(self)
391
- dialog = about(dialog, LOGO_PATH)
387
+ dialog = about(dialog)
392
388
  dialog.exec_()
393
389
 
394
390
  def open_euler_dialog(self):
@@ -420,7 +416,8 @@ class ElasticityGUI(QMainWindow):
420
416
  def crystal_elastic_plotter():
421
417
  app = QApplication(sys.argv)
422
418
  try:
423
- icon = QIcon(str(ICON_PATH))
419
+ path_to_icon = str(resources.files("elasticipy.resources") / "favicon.png")
420
+ icon = QIcon(str(path_to_icon))
424
421
  except Exception:
425
422
  icon = QIcon()
426
423
  app.setWindowIcon(icon)
@@ -1,6 +1,6 @@
1
1
  from elasticipy.tensors.second_order import SymmetricSecondOrderTensor, SecondOrderTensor, \
2
2
  SkewSymmetricSecondOrderTensor
3
- from elasticipy.tensors.stress_strain import StressTensor, StrainTensor
3
+ from elasticipy.tensors.stress_strain import StressTensor, StrainTensor, StrainRateTensor
4
4
  import pandas as pd
5
5
  import numpy as np
6
6
  import os
@@ -12,8 +12,8 @@ DTYPES={'stress':StressTensor,
12
12
  'strain_el':StrainTensor,
13
13
  'strain_pl':StrainTensor,
14
14
  'velgrad':SecondOrderTensor,
15
- 'defrate':SymmetricSecondOrderTensor,
16
- 'defrate_pl':SymmetricSecondOrderTensor,
15
+ 'defrate':StrainRateTensor,
16
+ 'defrate_pl':StrainRateTensor,
17
17
  'spinrate':SkewSymmetricSecondOrderTensor,
18
18
  }
19
19
 
@@ -10,8 +10,8 @@ def from_quadrature_file(file, returns='stress'):
10
10
  """
11
11
  Read data from quadrature output file generated by PRISMS plasticity.
12
12
 
13
- These files, usually named QuadratureOutputsXXX.csv (where XXX denotes the time increment), contain large amount of
14
- data, such as gradient components, stress values, grain ID etc.
13
+ These files, usually named ``QuadratureOutputsXXX.csv`` (where XXX denotes the time increment), contain large amount
14
+ of data, such as gradient components, stress values, grain ID etc.
15
15
 
16
16
  Parameters
17
17
  ----------
@@ -76,7 +76,7 @@ def from_stressstrain_file(file):
76
76
  """
77
77
  Read data from stress-strain file generated by PRISMS plasticity.
78
78
 
79
- This file is usually named stressstrain.txt
79
+ This file is usually named ``stressstrain.txt``.
80
80
 
81
81
  Parameters
82
82
  ----------
elasticipy/plasticity.py CHANGED
@@ -63,7 +63,7 @@ class IsotropicHardening:
63
63
  criterion: von Mises
64
64
  current strain: 0.0
65
65
 
66
- >>> JC.flow_stress(0.0) # Check that the yield stress = A
66
+ >>> print(JC.flow_stress(0.0)) # Check that the yield stress = A
67
67
  792.0
68
68
 
69
69
  In order to get the full tensile curve in 0 to 10% strain range:
Binary file
@@ -247,7 +247,7 @@ class SphericalFunction:
247
247
 
248
248
  The Young modulus along x direction is:
249
249
 
250
- >>> E.eval([1,0,0])
250
+ >>> print(E.eval([1,0,0]))
251
251
  74.4390243902439
252
252
 
253
253
  The Young moduli along a set a directions can be evaluated at once. E.g. along x, y and z:
@@ -305,7 +305,7 @@ class SphericalFunction:
305
305
  In spherical coordinates, the x direction is defined by theta=90° and phi=0. Therefore, the Young modulus along
306
306
  x direction is:
307
307
 
308
- >>> E.eval_spherical([0, 90], degrees=True)
308
+ >>> print(E.eval_spherical([0, 90], degrees=True))
309
309
  74.4390243902439
310
310
  """
311
311
  angles = np.atleast_2d(args)
@@ -627,6 +627,7 @@ class SphericalFunction:
627
627
  r = self.eval_spherical(angles)
628
628
  ax.plot(theta_polar, r, **kwargs)
629
629
  axs_new.append(ax)
630
+ new_fig.tight_layout()
630
631
  return new_fig, axs_new
631
632
 
632
633
  def plot_as_pole_figure(self, n_theta=50, n_phi=200, projection='lambert',
@@ -981,6 +982,7 @@ class HyperSphericalFunction(SphericalFunction):
981
982
  handles.extend([line, area])
982
983
  labels.extend([line.get_label(), area.get_label()])
983
984
  new_fig.legend(handles, labels, loc='upper center', ncol=2, bbox_to_anchor=(0.5, 0.95))
985
+ new_fig.tight_layout()
984
986
  return new_fig, axs
985
987
 
986
988
  def plot_as_pole_figure(self, n_theta=50, n_phi=200, n_psi=50, which='mean', projection='lambert', fig=None,
@@ -1032,7 +1034,7 @@ class HyperSphericalFunction(SphericalFunction):
1032
1034
  subplot_kwargs = {}
1033
1035
  if fig is None:
1034
1036
  fig = plt.figure()
1035
- ax = add_polefigure(fig, *subplot_kwargs, projection=projection, **subplot_kwargs)
1037
+ ax = add_polefigure(fig, *subplot_args, projection=projection, **subplot_kwargs)
1036
1038
  phi = np.linspace(*self.domain[0], n_phi)
1037
1039
  theta = np.linspace(*self.domain[1], n_theta)
1038
1040
  psi = np.linspace(*self.domain[2], n_psi)
@@ -725,6 +725,34 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
725
725
  -------
726
726
  HyperSphericalFunction
727
727
  Shear modulus
728
+
729
+ Examples
730
+ --------
731
+ Let's compute the shear modulus of an isotropic material, defined by its Young modulus and Poisson ratio:
732
+
733
+ >>> from elasticipy.tensors.elasticity import StiffnessTensor
734
+ >>> C = StiffnessTensor.isotropic(E=210, nu=0.27)
735
+ >>> C.shear_modulus
736
+ Hyperspherical function
737
+ Min=82.67716535433061, Max=82.67716535433077
738
+
739
+ For a cubic material (e.g. copper):
740
+
741
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
742
+ >>> G = C.shear_modulus
743
+ >>> G
744
+ Hyperspherical function
745
+ Min=26.000000000000007, Max=77.00000000000001
746
+
747
+ In details, the Poisson ratio between [1,0,0] and [0,1,0] directions is:
748
+
749
+ >>> print(G.eval([1,0,0],[0,1,0]))
750
+ 77.0
751
+
752
+ whereas:
753
+
754
+ >>> print(G.eval([1,1,0],[-1,1,0]))
755
+ 26.000000000000007
728
756
  """
729
757
  if isinstance(self, ComplianceTensor):
730
758
  S = self
@@ -756,6 +784,34 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
756
784
  \\nu_{ij}=-\\frac{\\partial \\varepsilon_{jj}}{\\partial \\varepsilon_{ii}}
757
785
 
758
786
  where :math:`\\varepsilon_{jj}` denotes the (compressive) longitudinal strain along the j-th direction.
787
+
788
+ Examples
789
+ --------
790
+ Let's compute the Poisson ratio of an isotropic material, defined by its Young and shear moduli:
791
+
792
+ >>> from elasticipy.tensors.elasticity import StiffnessTensor
793
+ >>> C = StiffnessTensor.isotropic(E=210, G=83)
794
+ >>> C.Poisson_ratio
795
+ Hyperspherical function
796
+ Min=0.2650602409638549, Max=0.2650602409638558
797
+
798
+ For a cubic material (e.g. copper):
799
+
800
+ >>> C = StiffnessTensor.cubic(C11=186, C12=134, C44=77)
801
+ >>> nu = C.Poisson_ratio
802
+ >>> nu
803
+ Hyperspherical function
804
+ Min=-0.09637908596800088, Max=0.789864502792421
805
+
806
+ In details, the Poisson ratio between [1,0,0] and [0,1,0] directions is:
807
+
808
+ >>> print(nu.eval([1,0,0],[0,1,0]))
809
+ 0.41875000000000007
810
+
811
+ whereas:
812
+
813
+ >>> print(nu.eval([1,1,0],[-1,1,0]))
814
+ -0.09637908596800099
759
815
  """
760
816
  if isinstance(self, ComplianceTensor):
761
817
  Sfull = self.full_tensor
@@ -850,7 +906,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
850
906
  else:
851
907
  return np.nan
852
908
 
853
- def Voigt_average(self, axis=None):
909
+ def Voigt_average(self, axis=None, orientations=None):
854
910
  """
855
911
  Compute the Voigt average (from the mean of stiffness tensors).
856
912
 
@@ -861,6 +917,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
861
917
  ----------
862
918
  axis : int, optional
863
919
  If provided, the average is computed along this axis. Otherwise, the mean is computed on the flattened array.
920
+ orientations : scipy.spatial.transform.Rotation or orix.quaternion.orientation.Orientation or crystal_texture.CrystalTexture or crystal_texture.CompositeTexture, optional
921
+ Orientation to use to compute the average. If the object is a single tensor, all the rotated tensor will be
922
+ computed accordingly, before computing the mean.
864
923
 
865
924
  Returns
866
925
  -------
@@ -909,6 +968,23 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
909
968
 
910
969
  >>> C_rotated.Voigt_average()
911
970
  Stiffness tensor (in Voigt mapping):
971
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 -4.39648318e-17
972
+ 0.00000000e+00 0.00000000e+00]
973
+ [ 1.34000000e+02 2.11474500e+02 1.08525500e+02 0.00000000e+00
974
+ 0.00000000e+00 0.00000000e+00]
975
+ [ 1.34000000e+02 1.08525500e+02 2.11474500e+02 0.00000000e+00
976
+ 0.00000000e+00 0.00000000e+00]
977
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 5.15255000e+01
978
+ 0.00000000e+00 0.00000000e+00]
979
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
980
+ 7.70000000e+01 -7.54674101e-17]
981
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
982
+ 7.54674101e-17 7.70000000e+01]]
983
+
984
+ A shorter way to do that is to pass the rotations to the function:
985
+
986
+ >>> C.Voigt_average(orientations=g)
987
+ Stiffness tensor (in Voigt mapping):
912
988
  [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 -4.39648318e-17
913
989
  0.00000000e+00 0.00000000e+00]
914
990
  [ 1.34000000e+02 2.11474500e+02 1.08525500e+02 0.00000000e+00
@@ -924,10 +1000,16 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
924
1000
  """
925
1001
  if self.ndim:
926
1002
  return self.mean(axis=axis)
927
- else:
1003
+ elif orientations is None:
928
1004
  return self.infinite_random_average()
1005
+ else:
1006
+ C_rotated = self * orientations
1007
+ if C_rotated.ndim:
1008
+ return C_rotated.Voigt_average()
1009
+ else:
1010
+ return C_rotated
929
1011
 
930
- def Reuss_average(self, axis=None):
1012
+ def Reuss_average(self, axis=None, orientations=None):
931
1013
  """
932
1014
  Compute the Reuss average (from the mean of compliance tensors).
933
1015
 
@@ -938,6 +1020,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
938
1020
  ----------
939
1021
  axis : int, optional
940
1022
  If provided, axis to compute the average along with. If none, the average is computed on the flattened array
1023
+ orientations : scipy.spatial.transform.Rotation or orix.quaternion.orientation.Orientation or crystal_texture.CrystalTexture or crystal_texture.CompositeTexture, optional
1024
+ Orientation to use to compute the average. If the object is a single tensor, all the rotated tensor will be
1025
+ computed accordingly, before computing the mean.
941
1026
 
942
1027
  Returns
943
1028
  -------
@@ -1004,10 +1089,27 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1004
1089
  7.70000000e+01 -7.54674101e-17]
1005
1090
  [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1006
1091
  7.54674101e-17 7.70000000e+01]]
1092
+
1093
+ A shorter way to do that is to pass the rotations to the function:
1094
+
1095
+ >>> C.Reuss_average(orientations=g)
1096
+ Stiffness tensor (in Voigt mapping):
1097
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 7.28375071e-16
1098
+ 0.00000000e+00 0.00000000e+00]
1099
+ [ 1.34000000e+02 1.98854548e+02 1.21145452e+02 -1.02540555e-15
1100
+ 0.00000000e+00 0.00000000e+00]
1101
+ [ 1.34000000e+02 1.21145452e+02 1.98854548e+02 -1.02540555e-15
1102
+ 0.00000000e+00 0.00000000e+00]
1103
+ [ 1.05096715e-14 1.06717815e-14 1.06717815e-14 3.88930441e+01
1104
+ 0.00000000e+00 0.00000000e+00]
1105
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1106
+ 7.70000000e+01 1.07632754e-16]
1107
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1108
+ -1.07632754e-16 7.70000000e+01]]
1007
1109
  """
1008
- return self.inv().Reuss_average(axis=axis).inv()
1110
+ return self.inv().Reuss_average(axis=axis, orientations=orientations).inv()
1009
1111
 
1010
- def Hill_average(self, axis=None):
1112
+ def Hill_average(self, axis=None, orientations=None):
1011
1113
  """
1012
1114
  Compute the Voigt-Reuss-Hill average (mean of Voigt and Reuss averages for stiffness tensors).
1013
1115
 
@@ -1018,6 +1120,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1018
1120
  ----------
1019
1121
  axis : int, optional
1020
1122
  If provided, axis to compute the average along with. If none, the average is computed on the flattened array
1123
+ orientations : scipy.spatial.transform.Rotation or orix.quaternion.orientation.Orientation or crystal_texture.CrystalTexture or crystal_texture.CompositeTexture, optional
1124
+ Orientation to use to compute the average. If the object is a single tensor, all the rotated tensor will be
1125
+ computed accordingly, before computing the mean.
1021
1126
 
1022
1127
  Returns
1023
1128
  -------
@@ -1084,12 +1189,29 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1084
1189
  7.70000000e+01 -7.54674101e-17]
1085
1190
  [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1086
1191
  7.54674101e-17 7.70000000e+01]]
1192
+
1193
+ A shorter way to do that is to pass the rotations to the function:
1194
+
1195
+ >>> C.Hill_average(orientations=g)
1196
+ Stiffness tensor (in Voigt mapping):
1197
+ [[ 1.86000000e+02 1.34000000e+02 1.34000000e+02 3.42205120e-16
1198
+ 0.00000000e+00 0.00000000e+00]
1199
+ [ 1.34000000e+02 2.05164524e+02 1.14835476e+02 -5.12702777e-16
1200
+ 0.00000000e+00 0.00000000e+00]
1201
+ [ 1.34000000e+02 1.14835476e+02 2.05164524e+02 -5.12702777e-16
1202
+ 0.00000000e+00 0.00000000e+00]
1203
+ [ 5.25483573e-15 5.33589076e-15 5.33589076e-15 4.52092721e+01
1204
+ 0.00000000e+00 0.00000000e+00]
1205
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1206
+ 7.70000000e+01 1.60826722e-17]
1207
+ [ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
1208
+ -1.60826722e-17 7.70000000e+01]]
1087
1209
  """
1088
- Reuss = self.Reuss_average(axis=axis)
1089
- Voigt = self.Voigt_average(axis=axis)
1210
+ Reuss = self.Reuss_average(axis=axis, orientations=orientations)
1211
+ Voigt = self.Voigt_average(axis=axis, orientations=orientations)
1090
1212
  return (Reuss + Voigt) * 0.5
1091
1213
 
1092
- def average(self, method, axis=None):
1214
+ def average(self, method, axis=None, orientations=None):
1093
1215
  """
1094
1216
  Compute either the Voigt, Reuss, or Hill average of the stiffness tensor.
1095
1217
 
@@ -1101,6 +1223,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1101
1223
  If provided, axis to compute the average along with. If none, the average is computed on the flattened array
1102
1224
  method : str {'Voigt', 'Reuss', 'Hill'}
1103
1225
  Method to use to compute the average.
1226
+ orientations : scipy.spatial.transform.Rotation or orix.quaternion.orientation.Orientation or crystal_texture.CrystalTexture or crystal_texture.CompositeTexture, optional
1227
+ Orientation to use to compute the average. If the object is a single tensor, all the rotated tensor will be
1228
+ computed accordingly, before computing the mean.
1104
1229
 
1105
1230
  Returns
1106
1231
  -------
@@ -1115,9 +1240,9 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1115
1240
  method = method.capitalize()
1116
1241
  if method in ('Voigt', 'Reuss', 'Hill'):
1117
1242
  fun = getattr(self, method + '_average')
1118
- return fun(axis=axis)
1243
+ return fun(axis=axis, orientations=orientations)
1119
1244
  else:
1120
- raise NotImplementedError('Only Voigt, Reus, and Hill are implemented.')
1245
+ raise NotImplementedError('Only Voigt, Reuss, and Hill are implemented.')
1121
1246
 
1122
1247
  @classmethod
1123
1248
  def isotropic(cls, E=None, nu=None, G=None, lame1=None, lame2=None, K=None, phase_name=None):
@@ -1612,7 +1737,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1612
1737
  --------
1613
1738
  >>> from elasticipy.tensors.elasticity import StiffnessTensor
1614
1739
  >>> C = StiffnessTensor.cubic(C11=200, C12=40, C44=20)
1615
- >>> C.Zener_ratio()
1740
+ >>> print(C.Zener_ratio())
1616
1741
  0.25
1617
1742
 
1618
1743
  which obvisouly corresponds to 2.C44/(C11-C12).
@@ -1959,7 +2084,7 @@ class StiffnessTensor(SymmetricFourthOrderTensor):
1959
2084
 
1960
2085
  Once rotated, it is not clear if the stiffness tensors has cubic symmetry on sight. Yet:
1961
2086
 
1962
- >>> C_rotated.is_cubic()
2087
+ >>> print(C_rotated.is_cubic())
1963
2088
  True
1964
2089
 
1965
2090
  """
@@ -2141,17 +2266,23 @@ class ComplianceTensor(StiffnessTensor):
2141
2266
  t.mapping = self.mapping.mapping_inverse
2142
2267
  return t
2143
2268
 
2144
- def Reuss_average(self, axis=None):
2269
+ def Reuss_average(self, axis=None, orientations=None):
2145
2270
  if self.ndim:
2146
2271
  return self.mean(axis=axis)
2147
- else:
2272
+ elif orientations is None:
2148
2273
  return self.infinite_random_average()
2274
+ else:
2275
+ C_rotated = self * orientations
2276
+ if C_rotated.ndim:
2277
+ return C_rotated.Reuss_average()
2278
+ else:
2279
+ return C_rotated
2149
2280
 
2150
- def Voigt_average(self, axis=None):
2151
- return self.inv().Voigt_average(axis=axis).inv()
2281
+ def Voigt_average(self, axis=None, orientations=None):
2282
+ return self.inv().Voigt_average(axis=axis, orientations=orientations).inv()
2152
2283
 
2153
- def Hill_average(self, axis=None):
2154
- return self.inv().Hill_average(axis=axis).inv()
2284
+ def Hill_average(self, axis=None, orientations=None):
2285
+ return self.inv().Hill_average(axis=axis, orientations=orientations).inv()
2155
2286
 
2156
2287
  @classmethod
2157
2288
  def isotropic(cls, E=None, nu=None, G=None, lame1=None, lame2=None, K=None, phase_name=None):
@@ -143,12 +143,12 @@ class FourthOrderTensor:
143
143
 
144
144
  For instance:
145
145
 
146
- >>> T_array[0,0,0,0]
146
+ >>> print(T_array[0,0,0,0])
147
147
  100.0
148
148
 
149
149
  whereas
150
150
 
151
- >>> T_array[0,1,0,1] # Corresponds to T_{66}/2
151
+ >>> print(T_array[0,1,0,1]) # Corresponds to T_{66}/2
152
152
  75.0
153
153
 
154
154
  The half factor comes from the Kelvin mapping convention (see Notes). One can also use the Voigt mapping to
@@ -168,17 +168,17 @@ class FourthOrderTensor:
168
168
  Although T and T_voigt appear to be the same, note that they are not expressed using the same mapping
169
169
  convention. Indeed:
170
170
 
171
- >>> T_voigt.full_tensor[0,0,0,0]
171
+ >>> print(T_voigt.full_tensor[0,0,0,0])
172
172
  100.0
173
173
 
174
174
  whereas
175
175
 
176
- >>> T_voigt.full_tensor[0,1,0,1]
176
+ >>> print(T_voigt.full_tensor[0,1,0,1])
177
177
  150.0
178
178
 
179
179
  Alternatively, the differences can be checked with:
180
180
 
181
- >>> T == T_voigt
181
+ >>> print(T == T_voigt)
182
182
  False
183
183
 
184
184
  Conversely, let consider the following Voigt matrix:
@@ -201,12 +201,12 @@ class FourthOrderTensor:
201
201
 
202
202
  Although T and T_voigt2 are not written using the same mapping, we can compare them:
203
203
 
204
- >>> T == T_voigt2 # Same tensors, but different mapping
204
+ >>> print(T == T_voigt2) # Same tensors, but different mapping
205
205
  True
206
206
 
207
207
  whereas
208
208
 
209
- >>> T == T_voigt # Different tensors, but same mapping
209
+ >>> print(T == T_voigt) # Different tensors, but same mapping
210
210
  False
211
211
 
212
212
  This property comes from the fact that the comparison is made independently of the underlying mapping convention.
@@ -643,10 +643,12 @@ class FourthOrderTensor:
643
643
  raise ValueError('The arrays to multiply could not be broadcasted with shapes {} and {}'.format(self.shape, other.shape[:-2]))
644
644
  elif isinstance(other, Rotation) or is_orix_rotation(other):
645
645
  return self.rotate(other)
646
- else:
646
+ elif isinstance(other, (float, int)):
647
647
  new_tensor = deepcopy(self)
648
648
  new_tensor._matrix = self._matrix * other
649
649
  return new_tensor
650
+ else:
651
+ return NotImplemented
650
652
 
651
653
  def __truediv__(self, other):
652
654
  if isinstance(other, (SecondOrderTensor, FourthOrderTensor)):
@@ -811,7 +813,7 @@ class FourthOrderTensor:
811
813
 
812
814
  Still, we have:
813
815
 
814
- >>> I == Iv
816
+ >>> print(I == Iv)
815
817
  True
816
818
 
817
819
  as they correspond to the same tensor, but expressed as a matrix with different mapping conventions. Indeed,
@@ -855,7 +857,7 @@ class FourthOrderTensor:
855
857
  mapping convention. Indeed, one can check that the full tensor is actually full of ones. E.g.:
856
858
 
857
859
  >>> tensor_of_ones.full_tensor[0,1,0,2]
858
- 1.0
860
+ np.float64(1.0)
859
861
 
860
862
  Alternatively, the Voigt mapping convention may help figuring it out:
861
863
 
@@ -872,7 +874,7 @@ class FourthOrderTensor:
872
874
 
873
875
  although both tensors are actually the same:
874
876
 
875
- >>> tensor_of_ones == tensor_of_ones_voigt
877
+ >>> print(tensor_of_ones == tensor_of_ones_voigt)
876
878
  True
877
879
  """
878
880
  return cls._broadcast_matrix(kelvin_mapping.matrix, shape=shape, **kwargs)
@@ -1239,6 +1241,25 @@ class FourthOrderTensor:
1239
1241
  a = deepcopy(self)
1240
1242
  return a
1241
1243
 
1244
+ def tensor_average(self, weights=None, axis=0):
1245
+ """
1246
+ Compute the average of a tensor array.
1247
+
1248
+ Parameters
1249
+ ----------
1250
+ weights : list of float or tuple of floats, optional
1251
+ If provided, each tensor value is weighted accordingly
1252
+ axis : int, optional
1253
+ Axis to compute the average over (default: 0)
1254
+
1255
+ Returns
1256
+ -------
1257
+ FourthOrderTensor
1258
+ """
1259
+ t = deepcopy(self)
1260
+ t._matrix = np.average(t._matrix, weights=weights, axis=axis)
1261
+ return t
1262
+
1242
1263
  class SymmetricFourthOrderTensor(FourthOrderTensor):
1243
1264
  _tensor_name = 'Symmetric 4th-order'
1244
1265