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/crystal_texture.py +849 -0
- elasticipy/gui/about.py +12 -10
- elasticipy/gui/gui.py +4 -7
- elasticipy/interfaces/FEPX.py +3 -3
- elasticipy/interfaces/PRISMS.py +3 -3
- elasticipy/plasticity.py +1 -1
- elasticipy/resources/logo_text.png +0 -0
- elasticipy/spherical_function.py +5 -3
- elasticipy/tensors/elasticity.py +149 -18
- elasticipy/tensors/fourth_order.py +32 -11
- elasticipy/tensors/second_order.py +119 -18
- elasticipy/tensors/stress_strain.py +41 -25
- {elasticipy-6.0.0.dist-info → elasticipy-6.1.0.dist-info}/METADATA +6 -4
- elasticipy-6.1.0.dist-info/RECORD +31 -0
- {elasticipy-6.0.0.dist-info → elasticipy-6.1.0.dist-info}/WHEEL +1 -1
- elasticipy-6.1.0.dist-info/entry_points.txt +2 -0
- elasticipy/resources/logo_text.svg +0 -126
- elasticipy-6.0.0.dist-info/RECORD +0 -30
- elasticipy-6.0.0.dist-info/entry_points.txt +0 -2
- {elasticipy-6.0.0.dist-info → elasticipy-6.1.0.dist-info}/licenses/LICENSE +0 -0
- {elasticipy-6.0.0.dist-info → elasticipy-6.1.0.dist-info}/top_level.txt +0 -0
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
|
|
6
|
-
dialog.setWindowTitle("About
|
|
6
|
+
def about(dialog):
|
|
7
|
+
dialog.setWindowTitle("About Elasticipy")
|
|
7
8
|
dialog.setFixedWidth(400)
|
|
8
9
|
|
|
9
10
|
layout = QVBoxLayout(dialog)
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
"©
|
|
24
|
+
"© 2025–2026 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
|
|
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
|
|
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
|
-
|
|
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)
|
elasticipy/interfaces/FEPX.py
CHANGED
|
@@ -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':
|
|
16
|
-
'defrate_pl':
|
|
15
|
+
'defrate':StrainRateTensor,
|
|
16
|
+
'defrate_pl':StrainRateTensor,
|
|
17
17
|
'spinrate':SkewSymmetricSecondOrderTensor,
|
|
18
18
|
}
|
|
19
19
|
|
elasticipy/interfaces/PRISMS.py
CHANGED
|
@@ -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
|
|
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
|
elasticipy/spherical_function.py
CHANGED
|
@@ -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, *
|
|
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)
|
elasticipy/tensors/elasticity.py
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|