elasticipy 2.8.10__tar.gz → 2.9.0__tar.gz

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.
Files changed (89) hide show
  1. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Example_Stiffness_tensor.py +1 -1
  2. elasticipy-2.9.0/Examples/essai_plasticity.py +73 -0
  3. {elasticipy-2.8.10/src/elasticipy.egg-info → elasticipy-2.9.0}/PKG-INFO +1 -1
  4. elasticipy-2.9.0/docs/source/Tutorials/Plasticity.rst +187 -0
  5. elasticipy-2.9.0/docs/source/Tutorials/images/Cyclic.png +0 -0
  6. elasticipy-2.9.0/docs/source/Tutorials/images/Incremental.png +0 -0
  7. elasticipy-2.9.0/docs/source/Tutorials/images/Shear.png +0 -0
  8. elasticipy-2.9.0/docs/source/Tutorials/images/Stress-controlled.png +0 -0
  9. elasticipy-2.9.0/docs/source/Tutorials/images/StressStrain-controlled.png +0 -0
  10. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials.rst +2 -1
  11. elasticipy-2.9.0/src/Elasticipy/Plasticity.py +282 -0
  12. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/SecondOrderTensor.py +97 -14
  13. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/StressStrainTensors.py +4 -3
  14. {elasticipy-2.8.10 → elasticipy-2.9.0/src/elasticipy.egg-info}/PKG-INFO +1 -1
  15. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/elasticipy.egg-info/SOURCES.txt +7 -0
  16. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/test_StressStrainTensors.py +14 -0
  17. elasticipy-2.9.0/tests/test_plasticity.py +131 -0
  18. elasticipy-2.8.10/src/Elasticipy/Plasticity.py +0 -125
  19. elasticipy-2.8.10/tests/test_plasticity.py +0 -57
  20. {elasticipy-2.8.10 → elasticipy-2.9.0}/.github/workflows/Codecov.yml +0 -0
  21. {elasticipy-2.8.10 → elasticipy-2.9.0}/.github/workflows/JOSS build.yml +0 -0
  22. {elasticipy-2.8.10 → elasticipy-2.9.0}/.github/workflows/cloc.yml +0 -0
  23. {elasticipy-2.8.10 → elasticipy-2.9.0}/.github/workflows/python-publish.yml +0 -0
  24. {elasticipy-2.8.10 → elasticipy-2.9.0}/.readthedocs.yaml +0 -0
  25. {elasticipy-2.8.10 → elasticipy-2.9.0}/CODE_OF_CONDUCT.md +0 -0
  26. {elasticipy-2.8.10 → elasticipy-2.9.0}/CONTRIBUTING.md +0 -0
  27. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Elasticipy_vs_pymatgen.py +0 -0
  28. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Elate_vs_Elasticipy.py +0 -0
  29. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Example_StressStrain_arrays.py +0 -0
  30. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Example_WaveVelocity.py +0 -0
  31. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/MaterialsProject.json +0 -0
  32. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/Multiple_phases.py +0 -0
  33. {elasticipy-2.8.10 → elasticipy-2.9.0}/Examples/example_readwrite.py +0 -0
  34. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/ElasticipyVSpymatgen.png +0 -0
  35. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/Nye.png +0 -0
  36. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/Plot_E.png +0 -0
  37. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/YoungModulus.png +0 -0
  38. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/paper.bib +0 -0
  39. {elasticipy-2.8.10 → elasticipy-2.9.0}/JOSS/paper.md +0 -0
  40. {elasticipy-2.8.10 → elasticipy-2.9.0}/LICENSE +0 -0
  41. {elasticipy-2.8.10 → elasticipy-2.9.0}/README.md +0 -0
  42. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/_static/images/HyperSphericalCoordinates.png +0 -0
  43. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/index.rst +0 -0
  44. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.FourthOrderTensor.rst +0 -0
  45. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.Plasticity.rst +0 -0
  46. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.SecondOrderTensor.rst +0 -0
  47. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.SphericalFunction.rst +0 -0
  48. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.StressStrainTensors.rst +0 -0
  49. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Elasticipy.rst +0 -0
  50. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/GUI.rst +0 -0
  51. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_AveragingMethods.rst +0 -0
  52. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_MultiplePhases.rst +0 -0
  53. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_ReadWriteFiles.rst +0 -0
  54. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_StiffnessTensor.rst +0 -0
  55. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_StressStrain.rst +0 -0
  56. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_ThermalExpansion.rst +0 -0
  57. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_wave-velocities.rst +0 -0
  58. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_PF.png +0 -0
  59. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_VRH_sections.png +0 -0
  60. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_hill_fiber.png +0 -0
  61. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_plot3D.png +0 -0
  62. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_xyz_sections.png +0 -0
  63. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/GUI.png +0 -0
  64. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_plot3D.png +0 -0
  65. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_plot3D_min.png +0 -0
  66. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_xyz_sections.png +0 -0
  67. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/WaveVelocities.png +0 -0
  68. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/Tutorials/images/plot_volumeFraction.png +0 -0
  69. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/conf.py +0 -0
  70. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/index.rst +0 -0
  71. {elasticipy-2.8.10 → elasticipy-2.9.0}/docs/source/modules.rst +0 -0
  72. {elasticipy-2.8.10 → elasticipy-2.9.0}/pyproject.toml +0 -0
  73. {elasticipy-2.8.10 → elasticipy-2.9.0}/requirements.txt +0 -0
  74. {elasticipy-2.8.10 → elasticipy-2.9.0}/setup.cfg +0 -0
  75. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/CrystalSymmetries.py +0 -0
  76. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/FourthOrderTensor.py +0 -0
  77. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/PoleFigure.py +0 -0
  78. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/SphericalFunction.py +0 -0
  79. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/ThermalExpansion.py +0 -0
  80. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/__init__.py +0 -0
  81. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/Elasticipy/gui.py +0 -0
  82. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/elasticipy.egg-info/dependency_links.txt +0 -0
  83. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/elasticipy.egg-info/requires.txt +0 -0
  84. {elasticipy-2.8.10 → elasticipy-2.9.0}/src/elasticipy.egg-info/top_level.txt +0 -0
  85. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/MaterialsProject.json +0 -0
  86. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/__init__.py +0 -0
  87. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/test_SphericalFunction.py +0 -0
  88. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/test_StiffnessTensor.py +0 -0
  89. {elasticipy-2.8.10 → elasticipy-2.9.0}/tests/test_ThermalExpansion.py +0 -0
@@ -22,7 +22,7 @@ E.plot_as_pole_figure() # or even with PF
22
22
  print(E.max())
23
23
 
24
24
  # Apply a random rotation on stiffness tensor
25
- rotation = Rotation.from_euler('zxz', [0, 45, 0], degrees=True)
25
+ rotation = Rotation.from_euler('ZXZ', [90, 45, 0], degrees=True)
26
26
  Crot = C*rotation
27
27
  # Check that the Young modulus has changed as well
28
28
  Crot.Young_modulus.plot3D()
@@ -0,0 +1,73 @@
1
+ from Elasticipy.Plasticity import JohnsonCook
2
+ from Elasticipy.StressStrainTensors import StressTensor, StrainTensor
3
+ from Elasticipy.FourthOrderTensor import StiffnessTensor
4
+ import numpy as np
5
+ from matplotlib import pyplot as plt
6
+ import matplotlib as mpl
7
+ mpl.use('Qt5Agg') # Ensure interactive plot
8
+
9
+
10
+ JC = JohnsonCook(A=363, B=792.7122, n=0.5756) # https://doi.org/10.1016/j.matpr.2020.05.213
11
+ C = StiffnessTensor.isotropic(E=210000, nu=0.27)
12
+
13
+ n_step = 100
14
+ sigma_max = 725
15
+ stress_mag = np.linspace(0, sigma_max, n_step)
16
+ stress = StressTensor.shear([1,0,0], [0,1,0],stress_mag)
17
+
18
+ elastic_strain = C.inv() * stress
19
+ plastic_strain = StrainTensor.zeros(n_step)
20
+ for i in range(2, n_step):
21
+ strain_increment = JC.compute_strain_increment(stress[i])
22
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
23
+
24
+
25
+ eps_xx = elastic_strain.C[0,1]+plastic_strain.C[0,1]
26
+ fig, ax = plt.subplots()
27
+ ax.plot(eps_xx, stress_mag, label='Stress-controlled')
28
+ ax.set_xlabel(r'$\varepsilon_{xy}$')
29
+ ax.set_ylabel('Shear stress (MPa)')
30
+
31
+ ##
32
+ from scipy.optimize import minimize_scalar
33
+ stress = StressTensor.zeros(n_step)
34
+ plastic_strain = StrainTensor.zeros(n_step)
35
+ JC.reset_strain()
36
+ for i in range(2, n_step):
37
+ def fun(tensile_stress):
38
+ trial_stress = StressTensor.shear([1,0,0], [0,1,0], tensile_stress)
39
+ trial_elastic_strain = C.inv() * trial_stress
40
+ trial_strain_increment = JC.compute_strain_increment(trial_stress, apply_strain=False)
41
+ trial_plastic_strain = plastic_strain[i - 1] + trial_strain_increment
42
+ trial_elongation = trial_plastic_strain.C[0,1] + trial_elastic_strain.C[0,1]
43
+ return (trial_elongation - eps_xx[i])**2
44
+ s = minimize_scalar(fun)
45
+ s0 = s.x
46
+ stress.C[0,1][i] = s0
47
+ strain_increment = JC.compute_strain_increment(stress[i])
48
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
49
+
50
+ ax.plot(eps_xx, stress.C[0,1], label='Strain-controlled', linestyle='dotted')
51
+ ax.legend()
52
+
53
+
54
+ #
55
+ JC.reset_strain()
56
+ JC_tresca = JohnsonCook(A=363, B=792.7122, n=0.5756, criterion='Tresca')
57
+ stress_mag = np.linspace(0, 500, n_step)
58
+ stress = StressTensor.shear([1,0,0], [0,1,0],stress_mag)
59
+ models = (JC, JC_tresca)
60
+ labels = ('von Mises', 'Tresca')
61
+
62
+ elastic_strain = C.inv() * stress
63
+ fig, ax = plt.subplots()
64
+ for j, model in enumerate(models):
65
+ plastic_strain = StrainTensor.zeros(n_step)
66
+ for i in range(2, n_step):
67
+ strain_increment = model.compute_strain_increment(stress[i])
68
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
69
+ eps_xy = elastic_strain.C[0,1]+plastic_strain.C[0,1]
70
+ ax.plot(eps_xy, stress_mag, label=labels[j])
71
+ ax.set_xlabel(r'$\varepsilon_{xy}$')
72
+ ax.set_ylabel('Shear stress (MPa)')
73
+ ax.legend()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: elasticipy
3
- Version: 2.8.10
3
+ Version: 2.9.0
4
4
  Summary: A Python library for elasticity tensor computations
5
5
  Author-email: Dorian Depriester <dorian.dep@gmail.com>
6
6
  License: MIT
@@ -0,0 +1,187 @@
1
+ Plasticity
2
+ ----------
3
+ Although Elasticipy was not initially meant to work on plasticity (hence the name...), it provides some clues about
4
+ simulations of elasto-plastic behaviour of materials. This tutorial shows how one can simulate a tensile test in the
5
+ elasto-plastic domain of a material.
6
+
7
+ The Johnson-Cook model
8
+ ======================
9
+ The Johnson-Cook (JC) model is widely used in the literature for modeling isotropic hardening of metalic materials.
10
+ Therefore, it is available out-of-the-box in Elasticipy. It assumes that the flow stress (:math:`\sigma`) depends on the
11
+ equivalent strain (:math:`\varepsilon`), the strain rate (:math:`\dot{\varepsilon}`) and the temperature (:math:`T`),
12
+ according to the following equation:
13
+
14
+ .. math::
15
+
16
+ \sigma = \left(A + B\varepsilon^n\right)
17
+ \left[1 + C\log\left(\frac{\varepsilon}{\dot{\varepsilon}_0}\right)\right]
18
+ \left(1-\theta^m\right)
19
+
20
+ with
21
+
22
+ .. math::
23
+
24
+ \theta = \begin{cases}
25
+ \frac{T-T_0}{T_m-T_0} & \text{if } T<T_m\\\\
26
+ 1 & \text{otherwise}
27
+ \end{cases}
28
+
29
+
30
+ :math:`A`, :math:`B`, :math:`C`, :math:`\dot{\varepsilon}_0`, :math:`T_0`, :math:`T_m` and :math:`m` are parameters
31
+ whose values depend on the material.
32
+
33
+ Simulation of a stress-controlled tensile test
34
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35
+ At first, we will try to simulate a stress-controlled tensile test. Although this approach is quite uncommon, we will do
36
+ this anyway, just because it is easier to program.
37
+
38
+ First, let us create the model:
39
+
40
+ .. doctest::
41
+
42
+ >>> from Elasticipy.Plasticity import JohnsonCook
43
+ >>> JC = JohnsonCook(A=363, B=792.7122, n=0.5756)
44
+
45
+ The parameters are taken from [1]_. As we will also take elastic behaviour into account, we also need:
46
+
47
+ >>> from Elasticipy.FourthOrderTensor import StiffnessTensor
48
+ >>> C = StiffnessTensor.isotropic(E=210000, nu=0.27)
49
+
50
+ Now, let say that we want to investigate the material's response for the tensile stress ranging from 0 to 725 MPa:
51
+
52
+ >>> from Elasticipy.StressStrainTensors import StressTensor, StrainTensor
53
+ >>> import numpy as np
54
+ >>> n_step = 100
55
+ >>> stress_mag = np.linspace(0, 725, n_step)
56
+ >>> stress = StressTensor.tensile([1,0,0], stress_mag)
57
+
58
+ At least, we can directly compute the elastic strain for each step:
59
+
60
+ >>> elastic_strain = C.inv() * stress
61
+
62
+ So now, the plastic strain can be computed using an iterative approach:
63
+
64
+ >>> plastic_strain = StrainTensor.zeros(n_step)
65
+ >>> for i in range(1, n_step):
66
+ strain_increment = JC.compute_strain_increment(stress[i])
67
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
68
+
69
+ That's all. Finally, let us plot the applied stress as a function of the overall elongation:
70
+
71
+ >>> from matplotlib import pyplot as plt
72
+ >>> elong = elastic_strain.C[0,0]+plastic_strain.C[0,0]
73
+ >>> fig, ax = plt.subplots()
74
+ >>> ax.plot(elong, stress_mag, label='Stress-controlled')
75
+ >>> ax.set_xlabel(r'$\varepsilon_{xx}$')
76
+ >>> ax.set_ylabel('Tensile stress (MPa)')
77
+
78
+ .. image:: images/Stress-controlled.png
79
+
80
+
81
+ Simulation of a strain-controlled tensile test
82
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83
+ The difficulty of simulating a strain-controlled tensile test is that, at a given step, one must identify both the
84
+ elastic and the plastic strain (if any) at once, while ensuring that the stress keeps uniaxial. Therefore, the hack is
85
+ to add a subroutine (optimization loop) to find the tensile stress so that the associated strain complies with the applied strain:
86
+
87
+ >>> from scipy.optimize import minimize_scalar
88
+ >>> JC.reset_strain() # Ensure that the previous hardening does not count
89
+ >>> stress = StressTensor.zeros(n_step)
90
+ >>> plastic_strain = StrainTensor.zeros(n_step)
91
+ >>> JC.reset_strain()
92
+ >>> for i in range(1, n_step):
93
+ def fun(tensile_stress):
94
+ trial_stress = StressTensor.tensile([1,0,0], tensile_stress)
95
+ trial_elastic_strain = C.inv() * trial_stress
96
+ trial_strain_increment = JC.compute_strain_increment(trial_stress, apply_strain=False)
97
+ trial_plastic_strain = plastic_strain[i - 1] + trial_strain_increment
98
+ trial_elongation = trial_plastic_strain.C[0,0] + trial_elastic_strain.C[0,0]
99
+ return (trial_elongation - elong[i])**2
100
+ s = minimize_scalar(fun)
101
+ stress.C[0,0][i] = s.x
102
+ strain_increment = JC.compute_strain_increment(stress[i])
103
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
104
+
105
+ Finally, let's plot the corresponding tensile curve ontop of that of the stress-controlled tensile test:
106
+
107
+ >>> ax.plot(elong, stress.C[0,0], label='Strain-controlled', linestyle='dotted')
108
+ >>> ax.legend()
109
+
110
+ .. image:: images/StressStrain-controlled.png
111
+
112
+
113
+ Incremental loading
114
+ ===================
115
+ Here, we have only considered monotonic loading, but we can also consider different loading path, such as incremental:
116
+
117
+ >>> load_path = [np.linspace(0,0.1),
118
+ np.linspace(0.1,0.099),
119
+ np.linspace(0.099,0.2),
120
+ np.linspace(0.2,0.199),
121
+ np.linspace(0.199,0.3)]
122
+ >>> elong = np.concatenate(load_path)
123
+ >>> n_step = len(elong)
124
+
125
+ .. image:: images/Incremental.png
126
+
127
+ or cyclic:
128
+
129
+
130
+ >>> load_path = [np.linspace(0,0.1),
131
+ np.linspace(0.1,-0.2),
132
+ np.linspace(-0.2,0.3),
133
+ np.linspace(0.3,-0.4)]
134
+ >>> elong = np.concatenate(load_path)
135
+ >>> n_step = len(elong)
136
+
137
+ .. image:: images/Cyclic.png
138
+
139
+ .. note::
140
+
141
+ The figure above clearly evidences the isotropic hardening inherent to the JC model.
142
+
143
+
144
+ Complex loading path
145
+ ====================
146
+ In the example above, we have only studied longitudinal stress/strain. Still, it is worth mentioning that other stress
147
+ states can be investigated (e.g. shear, multiaxial etc.) thanks to the
148
+ `normality rule <https://www.doitpoms.ac.uk/tlplib/granular_materials/normal.php>`_.
149
+
150
+ Tresca's plasticity criterion
151
+ =============================
152
+ Above, we have used the von Mises plasticity criterion (a.k.a J2 criterion). This can be switched to Tresca by passing
153
+ the plasticity criterion to the model constructor:
154
+
155
+ >>> JC = JohnsonCook(A=363, B=792.7122, n=0.5756, criterion='Tresca')
156
+
157
+ For instance, one can highlight the difference between the J2 and Tresca plasticity in shear:
158
+
159
+ >>> JC.reset_strain()
160
+ >>> JC_tresca = JohnsonCook(A=363, B=792.7122, n=0.5756, criterion='Tresca')
161
+ >>> stress_mag = np.linspace(0, 500, n_step)
162
+ >>> stress = StressTensor.shear([1,0,0], [0,1,0],stress_mag)
163
+ >>> models = (JC, JC_tresca)
164
+ >>> labels = ('von Mises', 'Tresca')
165
+ >>>
166
+ >>> elastic_strain = C.inv() * stress
167
+ >>> fig, ax = plt.subplots()
168
+ >>> for j, model in enumerate(models):
169
+ plastic_strain = StrainTensor.zeros(n_step)
170
+ for i in range(1, n_step):
171
+ strain_increment = model.compute_strain_increment(stress[i])
172
+ plastic_strain[i] = plastic_strain[i-1] + strain_increment
173
+ eps_xy = elastic_strain.C[0,1]+plastic_strain.C[0,1]
174
+ ax.plot(eps_xy, stress_mag, label=labels[j])
175
+ >>> ax.set_xlabel(r'$\varepsilon_{xy}$')
176
+ >>> ax.set_ylabel('Shear stress (MPa)')
177
+ >>> ax.legend()
178
+
179
+
180
+ .. image:: images/Shear.png
181
+
182
+
183
+
184
+ .. [1] Sandeep Yadav, Sorabh Singhal, Yogeshwar Jasra, Ravindra K. Saxena,
185
+ Determination of Johnson-Cook material model for weldment of mild steel,
186
+ Materials Today: Proceedings, Volume 28, Part 3, 2020, Pages 1801-1808, ISSN 2214-7853,
187
+ https://doi.org/10.1016/j.matpr.2020.05.213.
@@ -11,4 +11,5 @@ Tutorials
11
11
  ./Tutorials/Tutorial_ThermalExpansion.rst
12
12
  ./Tutorials/Tutorial_wave-velocities.rst
13
13
  ./Tutorials/Tutorial_MultiplePhases.rst
14
- ./Tutorials/Tutorial_ReadWriteFiles.rst
14
+ ./Tutorials/Tutorial_ReadWriteFiles.rst
15
+ ./Tutorials/Plasticity.rst
@@ -0,0 +1,282 @@
1
+ import numpy as np
2
+ from Elasticipy.StressStrainTensors import StrainTensor, StressTensor
3
+
4
+ class IsotropicHardening:
5
+ """
6
+ Template class for isotropic hardening plasticity models
7
+ """
8
+ def __init__(self, criterion='von Mises'):
9
+ """
10
+ Create an instance of a plastic model, assuming isotropic hardening
11
+
12
+ Parameters
13
+ ----------
14
+ criterion : str, optional
15
+ Plasticity criterion to use. Can be 'von Mises', 'Tresca' or 'J2'. J2 is the same as von Mises.
16
+ """
17
+ criterion = criterion.lower()
18
+ if criterion in ('von mises', 'mises', 'vonmises', 'j2'):
19
+ criterion = 'j2'
20
+ elif criterion != 'tresca':
21
+ raise ValueError('The criterion can be "Tresca", "von Mises" or "J2".')
22
+ self.criterion = criterion.lower()
23
+ self.plastic_strain = 0.0
24
+
25
+ def flow_stress(self, strain, **kwargs):
26
+ pass
27
+
28
+ def apply_strain(self, strain, **kwargs):
29
+ """
30
+ Apply strain to the current JC model.
31
+
32
+ This function updates the internal variable to store hardening state.
33
+
34
+ Parameters
35
+ ----------
36
+ strain : float or StrainTensor
37
+ kwargs : dict
38
+ Keyword arguments passed to flow_stress()
39
+
40
+ Returns
41
+ -------
42
+ float
43
+ Associated flow stress (positive)
44
+
45
+ See Also
46
+ --------
47
+ flow_stress : compute the flow stress, given a cumulative equivalent strain
48
+ """
49
+ if isinstance(strain, float):
50
+ self.plastic_strain += np.abs(strain)
51
+ elif isinstance(strain, StrainTensor):
52
+ self.plastic_strain += strain.eq_strain()
53
+ else:
54
+ raise ValueError('The applied strain must be float of StrainTensor')
55
+ return self.flow_stress(self.plastic_strain, **kwargs)
56
+
57
+ def compute_strain_increment(self, stress, **kwargs):
58
+ pass
59
+
60
+ def reset_strain(self):
61
+ self.plastic_strain = 0.0
62
+
63
+ def eq_stress(self, stress):
64
+ if self.criterion == 'j2':
65
+ return stress.vonMises()
66
+ else:
67
+ return stress.Tresca()
68
+
69
+
70
+ class JohnsonCook(IsotropicHardening):
71
+ def __init__(self, A, B, n, C=None, eps_dot_ref=1.0, m=None, T0=25, Tm=None, criterion='von Mises'):
72
+ """
73
+ Constructor for a Jonhson-Cook (JC) model.
74
+
75
+ The JC model is an exponential-law strain hardening model, which can take into account strain-rate sensibility
76
+ and temperature-dependence (although they are not mandatory). See notes for details.
77
+
78
+ Parameters
79
+ ----------
80
+ A : float
81
+ Yield stress
82
+ B : float
83
+ Work hardening coefficient
84
+ n : float
85
+ Work hardening exponent
86
+ C : float, optional
87
+ Strain-rate sensitivity coefficient
88
+ eps_dot_ref : float, optional
89
+ Reference strain-rate
90
+ m : float, optional
91
+ Temperature sensitivity exponent
92
+ T0 : float, optional
93
+ Reference temperature
94
+ Tm : float, optional
95
+ Melting temperature (at which the flow stress is zero)
96
+ criterion : str, optional
97
+ Plasticity criterion to use. It can be 'von Mises' or 'Tresca'.
98
+
99
+ Notes
100
+ -----
101
+ The flow stress (:math:`\\sigma`) depends on the strain (:math:`\\varepsilon`),
102
+ the strain rate :math:`\\dot{\\varepsilon}` and
103
+ the temperature (:math:`T`) so that:
104
+
105
+ .. math::
106
+
107
+ \\sigma = \\left(A + B\\varepsilon^n\\right)
108
+ \\left(1 + C\\log\\left(\\frac{\\varepsilon}{\\dot{\\varepsilon}_0}\\right)\\right)
109
+ \\left(1-\\theta^m\\right)
110
+
111
+ with
112
+
113
+ .. math::
114
+
115
+ \\theta = \\begin{cases}
116
+ \\frac{T-T_0}{T_m-T_0} & \\text{if } T<T_m\\\\
117
+ 1 & \\text{otherwise}
118
+ \\end{cases}
119
+ """
120
+ super().__init__(criterion=criterion)
121
+ self.A = A
122
+ self.B = B
123
+ self.C = C
124
+ self.n = n
125
+ self.m = m
126
+ self.eps_dot_ref = eps_dot_ref
127
+ self.T0 = T0
128
+ self.Tm = Tm
129
+
130
+ def flow_stress(self, eps_p, eps_dot=None, T=None):
131
+ """
132
+ Compute the flow stress from the Johnson-Cook model
133
+
134
+ Parameters
135
+ ----------
136
+ eps_p : float or list or tuple or numpy.ndarray
137
+ Equivalent plastic strain
138
+ eps_dot : float or list or tuple or numpy.ndarray, optional
139
+ Equivalent plastic strain rate. If float, the strain-rate is supposed to be homogeneous for every value of
140
+ eps_p.
141
+ T : float or list or tuple or np.ndarray
142
+ Temperature. If float, the temperature is supposed to be homogeneous for every value of eps_p.
143
+ Returns
144
+ -------
145
+ float or numpy.ndarray
146
+ Flow stress
147
+ """
148
+ eps_p = np.asarray(eps_p)
149
+ stress = (self.A + self.B * eps_p**self.n)
150
+
151
+ if eps_dot is not None:
152
+ eps_dot = np.asarray(eps_dot)
153
+ if (self.C is None) or (self.eps_dot_ref is None):
154
+ raise ValueError('C and eps_dot_ref must be defined for using a rate-dependent model')
155
+ stress *= (1 + self.C * np.log(eps_dot / self.eps_dot_ref))
156
+
157
+ if T is not None:
158
+ T = np.asarray(T)
159
+ if self.T0 is None or self.Tm is None or self.m is None:
160
+ raise ValueError('T0, Tm and m must be defined for using a temperature-dependent model')
161
+ theta = (T - self.T0) / (self.Tm - self.T0)
162
+ theta = np.clip(theta, None, 1.0)
163
+ stress *= (1 - theta**self.m)
164
+
165
+ return stress
166
+
167
+
168
+
169
+ def compute_strain_increment(self, stress, T=None, apply_strain=True, criterion='von Mises'):
170
+ """
171
+ Given the equivalent stress, compute the strain increment with respect to the normality rule.
172
+
173
+ Parameters
174
+ ----------
175
+ stress : float or StressTensor
176
+ Equivalent stress to compute the stress from, or full stress tensor.
177
+ T : float
178
+ Temperature
179
+ apply_strain : bool, optional
180
+ If true, the JC model will be updated to account for the applied strain (hardening)
181
+ criterion : str, optional
182
+ Plasticity criterion to consider to compute the equivalent stress and apply the normality rule.
183
+ It can be 'von Mises', 'Tresca' or 'J2'. 'J2' is equivalent to 'von Mises'.
184
+
185
+ Returns
186
+ -------
187
+ StrainTensor or float
188
+ Increment of plastic strain. If the input stress is float, only the magnitude of the increment will be
189
+ returned (float value). If the stress is of type StressTensor, the returned value will be a full
190
+ StrainTensor.
191
+
192
+ See Also
193
+ --------
194
+ apply_strain : apply strain to the JC model and updates its hardening value
195
+ """
196
+ if isinstance(stress, StressTensor):
197
+ eq_stress = self.eq_stress(stress)
198
+ else:
199
+ eq_stress = stress
200
+ if T is None:
201
+ if eq_stress > self.A:
202
+ k = eq_stress - self.A
203
+ total_strain = (1 / self.B * k) ** (1 / self.n)
204
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
205
+ else:
206
+ strain_increment = 0.0
207
+ else:
208
+ if self.T0 is None or self.Tm is None or self.m is None:
209
+ raise ValueError('T0, Tm and m must be defined for using a temperature-dependent model')
210
+ else:
211
+ if T >= self.Tm:
212
+ strain_increment = np.inf
213
+ else:
214
+ theta = (T - self.T0) / (self.Tm - self.T0)
215
+ theta_m = theta**self.m
216
+ k = (eq_stress / (1 - theta_m) - self.A)
217
+ if k<0:
218
+ strain_increment = 0.0
219
+ else:
220
+ total_strain = (1/self.B * k)**(1/self.n)
221
+ strain_increment = np.max((total_strain - self.plastic_strain, 0))
222
+ if apply_strain:
223
+ self.apply_strain(strain_increment)
224
+
225
+ if isinstance(stress, StressTensor):
226
+ n = normality_rule(stress, criterion=criterion)
227
+ return n * strain_increment
228
+ else:
229
+ return strain_increment
230
+
231
+ def reset_strain(self):
232
+ """
233
+ Reinitialize the plastic strain to 0
234
+ """
235
+ self.plastic_strain = 0.0
236
+
237
+
238
+ def normality_rule(stress, criterion='von Mises'):
239
+ """
240
+ Apply the normality rule for plastic flow, given a yield criterion.
241
+
242
+ The stress can be a single tensor, or an array of tensors.
243
+
244
+ Parameters
245
+ ----------
246
+ stress : StressTensor
247
+ Stress tensor to apply the normality rule from
248
+ criterion : str, optional
249
+ Name of the criterion to use. Can be either 'von Mises' or 'Tresca'
250
+
251
+ Returns
252
+ -------
253
+ StrainTensor
254
+ If a single stress tensor is passed, the returned array will be of shape
255
+
256
+ Notes
257
+ -----
258
+ The singular points for the Tresca criterion are treated as the von Mises criterion, which is equivalent to the
259
+ average of the two adjacent normals of the domain.
260
+ """
261
+ if criterion.lower()=='von mises':
262
+ eq_stress = stress.vonMises()
263
+ dev_stress= stress.deviatoric_part()
264
+ gradient_tensor = dev_stress / eq_stress
265
+ return StrainTensor(3/2 * gradient_tensor.matrix)
266
+ elif criterion.lower()=='tresca':
267
+ vals, dirs = stress.eig()
268
+ u1 = dirs[...,0]
269
+ u3 = dirs[...,2]
270
+ s1 = vals[...,0]
271
+ s2 = vals[..., 1]
272
+ s3 = vals[...,2]
273
+ A = np.einsum('...i,...j->...ij',u1, u1)
274
+ B = np.einsum('...i,...j->...ij',u3, u3)
275
+ normal = A - B
276
+ singular_points = np.logical_or(s2==s1, s2==s3)
277
+ normal[singular_points] = normality_rule(stress[singular_points], criterion='von Mises').matrix
278
+ normal[np.logical_and(s2==s1, s2==s3)] = 0.0
279
+ strain = StrainTensor(normal)
280
+ return strain / strain.eq_strain()
281
+ else:
282
+ raise NotImplementedError('The normality rule is only implemented for von Mises (J2) and Tresca criteria.')