elasticipy 2.8.9__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.
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Example_Stiffness_tensor.py +1 -1
- elasticipy-2.9.0/Examples/essai_plasticity.py +73 -0
- {elasticipy-2.8.9/src/elasticipy.egg-info → elasticipy-2.9.0}/PKG-INFO +1 -1
- elasticipy-2.9.0/docs/source/Tutorials/Plasticity.rst +187 -0
- elasticipy-2.9.0/docs/source/Tutorials/images/Cyclic.png +0 -0
- elasticipy-2.9.0/docs/source/Tutorials/images/Incremental.png +0 -0
- elasticipy-2.9.0/docs/source/Tutorials/images/Shear.png +0 -0
- elasticipy-2.9.0/docs/source/Tutorials/images/Stress-controlled.png +0 -0
- elasticipy-2.9.0/docs/source/Tutorials/images/StressStrain-controlled.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials.rst +2 -1
- elasticipy-2.9.0/src/Elasticipy/Plasticity.py +282 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/SecondOrderTensor.py +99 -15
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/StressStrainTensors.py +4 -3
- {elasticipy-2.8.9 → elasticipy-2.9.0/src/elasticipy.egg-info}/PKG-INFO +1 -1
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/elasticipy.egg-info/SOURCES.txt +7 -3
- {elasticipy-2.8.9 → elasticipy-2.9.0}/tests/test_StiffnessTensor.py +18 -2
- {elasticipy-2.8.9 → elasticipy-2.9.0}/tests/test_StressStrainTensors.py +14 -0
- elasticipy-2.9.0/tests/test_plasticity.py +131 -0
- elasticipy-2.8.9/.github/workflows/conda-publish.yml +0 -53
- elasticipy-2.8.9/conda/build.sh +0 -3
- elasticipy-2.8.9/conda/meta.yaml +0 -15
- elasticipy-2.8.9/src/Elasticipy/Plasticity.py +0 -125
- elasticipy-2.8.9/tests/test_plasticity.py +0 -57
- {elasticipy-2.8.9 → elasticipy-2.9.0}/.github/workflows/Codecov.yml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/.github/workflows/JOSS build.yml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/.github/workflows/cloc.yml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/.github/workflows/python-publish.yml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/.readthedocs.yaml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/CODE_OF_CONDUCT.md +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/CONTRIBUTING.md +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Elasticipy_vs_pymatgen.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Elate_vs_Elasticipy.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Example_StressStrain_arrays.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Example_WaveVelocity.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/MaterialsProject.json +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/Multiple_phases.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/Examples/example_readwrite.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/ElasticipyVSpymatgen.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/Nye.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/Plot_E.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/YoungModulus.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/paper.bib +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/JOSS/paper.md +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/LICENSE +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/README.md +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/_static/images/HyperSphericalCoordinates.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/index.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.FourthOrderTensor.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.Plasticity.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.SecondOrderTensor.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.SphericalFunction.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.StressStrainTensors.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Elasticipy.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/GUI.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_AveragingMethods.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_MultiplePhases.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_ReadWriteFiles.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_StiffnessTensor.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_StressStrain.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_ThermalExpansion.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/Tutorial_wave-velocities.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_PF.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_VRH_sections.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_hill_fiber.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_plot3D.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/E_xyz_sections.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/GUI.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_plot3D.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_plot3D_min.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/G_xyz_sections.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/WaveVelocities.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/Tutorials/images/plot_volumeFraction.png +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/conf.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/index.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/docs/source/modules.rst +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/pyproject.toml +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/requirements.txt +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/setup.cfg +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/CrystalSymmetries.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/FourthOrderTensor.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/PoleFigure.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/SphericalFunction.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/ThermalExpansion.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/__init__.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/Elasticipy/gui.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/elasticipy.egg-info/dependency_links.txt +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/elasticipy.egg-info/requires.txt +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/src/elasticipy.egg-info/top_level.txt +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/tests/MaterialsProject.json +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/tests/__init__.py +0 -0
- {elasticipy-2.8.9 → elasticipy-2.9.0}/tests/test_SphericalFunction.py +0 -0
- {elasticipy-2.8.9 → 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('
|
|
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()
|
|
@@ -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.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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.')
|