elasticipy 2.8.10__tar.gz → 3.0.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.10 → elasticipy-3.0.0}/Examples/Example_Stiffness_tensor.py +1 -1
- elasticipy-3.0.0/Examples/essai_plasticity.py +73 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/paper.bib +4 -2
- {elasticipy-2.8.10/src/elasticipy.egg-info → elasticipy-3.0.0}/PKG-INFO +2 -1
- {elasticipy-2.8.10 → elasticipy-3.0.0}/README.md +1 -0
- elasticipy-3.0.0/docs/source/Tutorials/Plasticity.rst +187 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_StiffnessTensor.rst +1 -1
- elasticipy-3.0.0/docs/source/Tutorials/Tutorial_StressStrain.rst +231 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_ThermalExpansion.rst +9 -5
- elasticipy-3.0.0/docs/source/Tutorials/images/Cyclic.png +0 -0
- elasticipy-3.0.0/docs/source/Tutorials/images/Incremental.png +0 -0
- elasticipy-3.0.0/docs/source/Tutorials/images/Shear.png +0 -0
- elasticipy-3.0.0/docs/source/Tutorials/images/Stress-controlled.png +0 -0
- elasticipy-3.0.0/docs/source/Tutorials/images/StressStrain-controlled.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials.rst +2 -1
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/index.rst +4 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/FourthOrderTensor.py +360 -44
- elasticipy-3.0.0/src/Elasticipy/Plasticity.py +332 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/SecondOrderTensor.py +338 -66
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/StressStrainTensors.py +13 -8
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/ThermalExpansion.py +88 -17
- {elasticipy-2.8.10 → elasticipy-3.0.0/src/elasticipy.egg-info}/PKG-INFO +2 -1
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/elasticipy.egg-info/SOURCES.txt +7 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/test_StiffnessTensor.py +147 -9
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/test_StressStrainTensors.py +182 -16
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/test_ThermalExpansion.py +15 -0
- elasticipy-3.0.0/tests/test_plasticity.py +148 -0
- elasticipy-2.8.10/docs/source/Tutorials/Tutorial_StressStrain.rst +0 -445
- elasticipy-2.8.10/src/Elasticipy/Plasticity.py +0 -125
- elasticipy-2.8.10/tests/test_plasticity.py +0 -57
- {elasticipy-2.8.10 → elasticipy-3.0.0}/.github/workflows/Codecov.yml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/.github/workflows/JOSS build.yml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/.github/workflows/cloc.yml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/.github/workflows/python-publish.yml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/.readthedocs.yaml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/CODE_OF_CONDUCT.md +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/CONTRIBUTING.md +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/Elasticipy_vs_pymatgen.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/Elate_vs_Elasticipy.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/Example_StressStrain_arrays.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/Example_WaveVelocity.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/MaterialsProject.json +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/Multiple_phases.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/Examples/example_readwrite.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/ElasticipyVSpymatgen.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/Nye.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/Plot_E.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/YoungModulus.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/JOSS/paper.md +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/LICENSE +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/_static/images/HyperSphericalCoordinates.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/index.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.FourthOrderTensor.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.Plasticity.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.SecondOrderTensor.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.SphericalFunction.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.StressStrainTensors.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Elasticipy.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/GUI.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_AveragingMethods.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_MultiplePhases.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_ReadWriteFiles.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/Tutorial_wave-velocities.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/E_PF.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/E_VRH_sections.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/E_hill_fiber.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/E_plot3D.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/E_xyz_sections.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/GUI.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/G_plot3D.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/G_plot3D_min.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/G_xyz_sections.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/WaveVelocities.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/Tutorials/images/plot_volumeFraction.png +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/conf.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/docs/source/modules.rst +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/pyproject.toml +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/requirements.txt +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/setup.cfg +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/CrystalSymmetries.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/PoleFigure.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/SphericalFunction.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/__init__.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/Elasticipy/gui.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/elasticipy.egg-info/dependency_links.txt +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/elasticipy.egg-info/requires.txt +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/src/elasticipy.egg-info/top_level.txt +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/MaterialsProject.json +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/__init__.py +0 -0
- {elasticipy-2.8.10 → elasticipy-3.0.0}/tests/test_SphericalFunction.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()
|
|
@@ -6,6 +6,7 @@ pages = {314-319},
|
|
|
6
6
|
year = {2013},
|
|
7
7
|
issn = {0927-0256},
|
|
8
8
|
url = {https://doi.org/10.1016/j.commatsci.2012.10.028},
|
|
9
|
+
doi = {10.1016/j.commatsci.2012.10.028},
|
|
9
10
|
author = {Shyue Ping Ong and William Davidson Richards and Anubhav Jain and Geoffroy Hautier and Michael Kocher and Shreyas Cholia and Dan Gunter and Vincent L. Chevrier and Kristin A. Persson and Gerbrand Ceder},
|
|
10
11
|
keywords = {Materials, Project, Design, Thermodynamics, High-throughput},
|
|
11
12
|
}
|
|
@@ -33,7 +34,8 @@ journal = {Journal of Physics: Condensed Matter},
|
|
|
33
34
|
pages={175--192},
|
|
34
35
|
year={2011},
|
|
35
36
|
publisher={The Geological Society of London London},
|
|
36
|
-
url={http://dx.doi.org/10.1144/SP360.10}
|
|
37
|
+
url={http://dx.doi.org/10.1144/SP360.10},
|
|
38
|
+
doi={10.1144/SP360.10}
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
@book{nye,
|
|
@@ -124,4 +126,4 @@ DOI = {10.5194/se-11-259-2020}
|
|
|
124
126
|
doi = {10.1038/s41586-020-2649-2},
|
|
125
127
|
publisher = {Springer Science and Business Media {LLC}},
|
|
126
128
|
url = {https://doi.org/10.1038/s41586-020-2649-2}
|
|
127
|
-
}
|
|
129
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: elasticipy
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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
|
|
@@ -36,6 +36,7 @@ Requires-Dist: mp_api; extra == "dev"
|
|
|
36
36
|
[](https://doi.org/10.5281/zenodo.14501849)
|
|
37
37
|
[](https://codecov.io/gh/DorianDepriester/Elasticipy)
|
|
38
38
|

|
|
39
|
+
[](https://joss.theoj.org/papers/8cce91b782f17f52e9ee30916cd86ad5)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](https://doi.org/10.5281/zenodo.14501849)
|
|
7
7
|
[](https://codecov.io/gh/DorianDepriester/Elasticipy)
|
|
8
8
|

|
|
9
|
+
[](https://joss.theoj.org/papers/8cce91b782f17f52e9ee30916cd86ad5)
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
|
|
@@ -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') # doctest: +SKIP
|
|
75
|
+
>>> ax.set_xlabel(r'$\varepsilon_{xx}$') # doctest: +SKIP
|
|
76
|
+
>>> ax.set_ylabel('Tensile stress (MPa)') # doctest: +SKIP
|
|
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') # doctest: +SKIP
|
|
108
|
+
>>> ax.legend() # doctest: +SKIP
|
|
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}$') # doctest: +SKIP
|
|
176
|
+
>>> ax.set_ylabel('Shear stress (MPa)') # doctest: +SKIP
|
|
177
|
+
>>> ax.legend() # doctest: +SKIP
|
|
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.
|
|
@@ -55,7 +55,7 @@ deviation of the Young modulus:
|
|
|
55
55
|
Another way to evidence anisotropy is to use the universal anisotropy factor [1]_:
|
|
56
56
|
|
|
57
57
|
>>> C.universal_anisotropy
|
|
58
|
-
5.
|
|
58
|
+
5.1410095516414085
|
|
59
59
|
|
|
60
60
|
Shear moduli and Poisson ratios
|
|
61
61
|
-------------------------------
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Stress and strain tensors
|
|
2
|
+
=========================
|
|
3
|
+
|
|
4
|
+
This tutorial illustrates how we work on strain and stress tensors, and how Elasticipy handles arrays of tensors.
|
|
5
|
+
|
|
6
|
+
Single tensors
|
|
7
|
+
--------------
|
|
8
|
+
Let's start with basic operations with the stress tensor. For instance, we can compute the von Mises and Tresca
|
|
9
|
+
equivalent stresses:
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
.. doctest::
|
|
13
|
+
|
|
14
|
+
>>> from Elasticipy.StressStrainTensors import StressTensor, StrainTensor
|
|
15
|
+
>>> stress = StressTensor.shear([1, 0, 0], [0, 1, 0], 1.0) # Unit XY shear stress
|
|
16
|
+
>>> print(stress.vonMises(), stress.Tresca())
|
|
17
|
+
1.7320508075688772 2.0
|
|
18
|
+
|
|
19
|
+
So now, let's have a look on the strain tensor, and compute the principal strains and the volumetric change:
|
|
20
|
+
|
|
21
|
+
>>> strain = StrainTensor.shear([1,0,0], [0,1,0], 1e-3) # XY Shear strain with 1e-3 mag.
|
|
22
|
+
>>> print(strain.principal_strains())
|
|
23
|
+
[ 0.001 0. -0.001]
|
|
24
|
+
>>> print(strain.volumetric_strain())
|
|
25
|
+
0.0
|
|
26
|
+
>>> stress = StressTensor.shear([1, 0, 0], [0, 1, 0], 1.0) # Unit XY shear stress
|
|
27
|
+
>>> print(stress.vonMises(), stress.Tresca())
|
|
28
|
+
1.7320508075688772 2.0
|
|
29
|
+
|
|
30
|
+
Linear elasticity
|
|
31
|
+
--------------------------------
|
|
32
|
+
This section is dedicated to linear elasticity, hence introducing the fourth-order stiffness tensor.
|
|
33
|
+
As an example, create a stiffness tensor corresponding to steel:
|
|
34
|
+
|
|
35
|
+
>>> from Elasticipy.FourthOrderTensor import StiffnessTensor
|
|
36
|
+
>>> C = StiffnessTensor.isotropic(E=210e3, nu=0.28)
|
|
37
|
+
>>> print(C)
|
|
38
|
+
Stiffness tensor (in Voigt notation):
|
|
39
|
+
[[268465.90909091 104403.40909091 104403.40909091 0.
|
|
40
|
+
0. 0. ]
|
|
41
|
+
[104403.40909091 268465.90909091 104403.40909091 0.
|
|
42
|
+
0. 0. ]
|
|
43
|
+
[104403.40909091 104403.40909091 268465.90909091 0.
|
|
44
|
+
0. 0. ]
|
|
45
|
+
[ 0. 0. 0. 82031.25
|
|
46
|
+
0. 0. ]
|
|
47
|
+
[ 0. 0. 0. 0.
|
|
48
|
+
82031.25 0. ]
|
|
49
|
+
[ 0. 0. 0. 0.
|
|
50
|
+
0. 82031.25 ]]
|
|
51
|
+
Symmetry: isotropic
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Considering the previous strain, evaluate the corresponding stress:
|
|
55
|
+
|
|
56
|
+
>>> sigma = C * strain
|
|
57
|
+
>>> print(sigma)
|
|
58
|
+
Stress tensor
|
|
59
|
+
[[ 0. 164.0625 0. ]
|
|
60
|
+
[164.0625 0. 0. ]
|
|
61
|
+
[ 0. 0. 0. ]]
|
|
62
|
+
|
|
63
|
+
Conversely, one can compute the compliance tensor:
|
|
64
|
+
|
|
65
|
+
>>> S = C.inv()
|
|
66
|
+
>>> print(S)
|
|
67
|
+
Compliance tensor (in Voigt notation):
|
|
68
|
+
[[ 4.76190476e-06 -1.33333333e-06 -1.33333333e-06 0.00000000e+00
|
|
69
|
+
0.00000000e+00 0.00000000e+00]
|
|
70
|
+
[-1.33333333e-06 4.76190476e-06 -1.33333333e-06 0.00000000e+00
|
|
71
|
+
0.00000000e+00 0.00000000e+00]
|
|
72
|
+
[-1.33333333e-06 -1.33333333e-06 4.76190476e-06 0.00000000e+00
|
|
73
|
+
0.00000000e+00 0.00000000e+00]
|
|
74
|
+
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.21904762e-05
|
|
75
|
+
0.00000000e+00 0.00000000e+00]
|
|
76
|
+
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
77
|
+
1.21904762e-05 0.00000000e+00]
|
|
78
|
+
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
|
|
79
|
+
0.00000000e+00 1.21904762e-05]]
|
|
80
|
+
Symmetry: isotropic
|
|
81
|
+
|
|
82
|
+
and check that we retrieve the correct (initial) strain:
|
|
83
|
+
|
|
84
|
+
>>> print(S * sigma)
|
|
85
|
+
Strain tensor
|
|
86
|
+
[[0. 0.001 0. ]
|
|
87
|
+
[0.001 0. 0. ]
|
|
88
|
+
[0. 0. 0. ]]
|
|
89
|
+
|
|
90
|
+
.. _multidimensional-arrays:
|
|
91
|
+
|
|
92
|
+
Multidimensional tensor arrays
|
|
93
|
+
------------------------------
|
|
94
|
+
Elasticipy allows to process thousands of tensors at one, with the aid of tensor arrays.
|
|
95
|
+
As an illustration, we consider the anisotropic behaviour of ferrite:
|
|
96
|
+
|
|
97
|
+
>>> C = StiffnessTensor.fromCrystalSymmetry(symmetry='cubic', phase_name='ferrite',
|
|
98
|
+
... C11=274, C12=175, C44=89)
|
|
99
|
+
>>> print(C)
|
|
100
|
+
Stiffness tensor (in Voigt notation) for ferrite:
|
|
101
|
+
[[274. 175. 175. 0. 0. 0.]
|
|
102
|
+
[175. 274. 175. 0. 0. 0.]
|
|
103
|
+
[175. 175. 274. 0. 0. 0.]
|
|
104
|
+
[ 0. 0. 0. 89. 0. 0.]
|
|
105
|
+
[ 0. 0. 0. 0. 89. 0.]
|
|
106
|
+
[ 0. 0. 0. 0. 0. 89.]]
|
|
107
|
+
Symmetry: cubic
|
|
108
|
+
|
|
109
|
+
Let's start by creating an array of 10 stresses:
|
|
110
|
+
|
|
111
|
+
>>> import numpy as np
|
|
112
|
+
>>> n_array = 10
|
|
113
|
+
>>> shear_stress = np.linspace(0, 100, n_array)
|
|
114
|
+
>>> sigma = StressTensor.shear([1,0,0],[0,1,0], shear_stress) # Array of stresses corresponding to X-Y shear
|
|
115
|
+
>>> print(sigma[0]) # Check the initial value of the stress...
|
|
116
|
+
Stress tensor
|
|
117
|
+
[[0. 0. 0.]
|
|
118
|
+
[0. 0. 0.]
|
|
119
|
+
[0. 0. 0.]]
|
|
120
|
+
>>> print(sigma[-1]) # ...and the final value.
|
|
121
|
+
Stress tensor
|
|
122
|
+
[[ 0. 100. 0.]
|
|
123
|
+
[100. 0. 0.]
|
|
124
|
+
[ 0. 0. 0.]]
|
|
125
|
+
|
|
126
|
+
The corresponding strain array is evaluated with the same syntax as before:
|
|
127
|
+
|
|
128
|
+
>>> eps = C.inv() * sigma
|
|
129
|
+
>>> print(eps[0]) # Now check the initial value of strain...
|
|
130
|
+
Strain tensor
|
|
131
|
+
[[0. 0. 0.]
|
|
132
|
+
[0. 0. 0.]
|
|
133
|
+
[0. 0. 0.]]
|
|
134
|
+
>>> print(eps[-1]) # ...and the final value.
|
|
135
|
+
Strain tensor
|
|
136
|
+
[[0. 0.56179775 0. ]
|
|
137
|
+
[0.56179775 0. 0. ]
|
|
138
|
+
[0. 0. 0. ]]
|
|
139
|
+
|
|
140
|
+
We can for instance compute the corresponding elastic energies:
|
|
141
|
+
|
|
142
|
+
>>> print(eps.elastic_energy(sigma))
|
|
143
|
+
[ 0. 0.69357747 2.77430989 6.24219725 11.09723956 17.33943682
|
|
144
|
+
24.96878901 33.98529616 44.38895825 56.17977528]
|
|
145
|
+
|
|
146
|
+
Another application of working with an array of stress tensors is to check whether a tensor field complies with the
|
|
147
|
+
balance of linear momentum (see `here <https://en.wikiversity.org/wiki/Continuum_mechanics/Balance_of_linear_momentum>`_
|
|
148
|
+
for details) or not. For instance, if we want to compute the divergence of ``sigma``:
|
|
149
|
+
|
|
150
|
+
>>> sigma.div()
|
|
151
|
+
array([[ 0. , 11.11111111, 0. ],
|
|
152
|
+
[ 0. , 11.11111111, 0. ],
|
|
153
|
+
[ 0. , 11.11111111, 0. ],
|
|
154
|
+
[ 0. , 11.11111111, 0. ],
|
|
155
|
+
[ 0. , 11.11111111, 0. ],
|
|
156
|
+
[ 0. , 11.11111111, 0. ],
|
|
157
|
+
[ 0. , 11.11111111, 0. ],
|
|
158
|
+
[ 0. , 11.11111111, 0. ],
|
|
159
|
+
[ 0. , 11.11111111, 0. ],
|
|
160
|
+
[ 0. , 11.11111111, 0. ]])
|
|
161
|
+
|
|
162
|
+
Here, the *i*-th row provides the divergence vector for the *i*-th stress tensor.
|
|
163
|
+
See `the full documentation <../Elasticipy.SecondOrderTensor.html#Elasticipy.SecondOrderTensor.SecondOrderTensor.div>`_ for
|
|
164
|
+
details about this function.
|
|
165
|
+
|
|
166
|
+
.. _strain_rotations:
|
|
167
|
+
|
|
168
|
+
Apply rotations
|
|
169
|
+
---------------
|
|
170
|
+
Rotations can be applied on the tensors. If multiple rotations are applied at once, this results in tensor arrays.
|
|
171
|
+
Rotations are defined by ``scipy.transform.Rotation``
|
|
172
|
+
(see `here <https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.transform.Rotation.html>`__ for details).
|
|
173
|
+
|
|
174
|
+
>>> from scipy.spatial.transform import Rotation
|
|
175
|
+
|
|
176
|
+
For example, let's consider a random set of 1000 rotations:
|
|
177
|
+
|
|
178
|
+
>>> n_ori = 1000
|
|
179
|
+
>>> random_state = 1234 # This is just to ensure reproducibility
|
|
180
|
+
>>> rotations = Rotation.random(n_ori, random_state=random_state)
|
|
181
|
+
|
|
182
|
+
These rotations can be applied on the strain tensor
|
|
183
|
+
|
|
184
|
+
>>> eps_rotated = eps.rotate(rotations, mode='cross')
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
Option ``mode='cross'`` allows to compute all combinations of strains and rotation, resulting in a kind of 2D matrix of
|
|
188
|
+
strain tensors:
|
|
189
|
+
|
|
190
|
+
>>> print(eps_rotated.shape)
|
|
191
|
+
(10, 1000)
|
|
192
|
+
|
|
193
|
+
Therefore, we can compute the corresponding rotated stress array:
|
|
194
|
+
|
|
195
|
+
>>> sigma_rotated = C * eps_rotated
|
|
196
|
+
>>> print(sigma_rotated.shape) # Check out the shape of the stresses
|
|
197
|
+
(10, 1000)
|
|
198
|
+
|
|
199
|
+
And get the stress back to the initial coordinate system:
|
|
200
|
+
|
|
201
|
+
>>> sigma = sigma_rotated * rotations.inv() # Go back to initial frame
|
|
202
|
+
|
|
203
|
+
As opposed to the ``rotate(..., mode='cross')`` (see above), we use ``*`` here to keep the same
|
|
204
|
+
dimensionality (perform element-wise multiplication). It is equivalent to:
|
|
205
|
+
|
|
206
|
+
>>> sigma = sigma_rotated.rotate(rotations.inv())
|
|
207
|
+
|
|
208
|
+
Finally, we can estimate the mean stresses among all the orientations:
|
|
209
|
+
|
|
210
|
+
>>> sigma_mean = sigma.mean(axis=1) # Compute the mean over all orientations
|
|
211
|
+
>>> print(sigma_mean[-1]) # random
|
|
212
|
+
Stress tensor
|
|
213
|
+
[[ 5.35134832e-01 8.22419895e+01 2.02619662e-01]
|
|
214
|
+
[ 8.22419895e+01 -4.88440590e-01 -1.52733598e-01]
|
|
215
|
+
[ 2.02619662e-01 -1.52733598e-01 -4.66942413e-02]]
|
|
216
|
+
|
|
217
|
+
Actually, a more straightforward method is to define a set of rotated stiffness tensors, and compute their Reuss average:
|
|
218
|
+
|
|
219
|
+
>>> C_rotated = C * rotations
|
|
220
|
+
>>> C_Voigt = C_rotated.Voigt_average()
|
|
221
|
+
|
|
222
|
+
Which yields the same results in terms of stress:
|
|
223
|
+
|
|
224
|
+
>>> sigma_Voigt = C_Voigt * eps
|
|
225
|
+
>>> print(sigma_Voigt[-1])
|
|
226
|
+
Stress tensor
|
|
227
|
+
[[ 5.35134832e-01 8.22419895e+01 2.02619662e-01]
|
|
228
|
+
[ 8.22419895e+01 -4.88440590e-01 -1.52733598e-01]
|
|
229
|
+
[ 2.02619662e-01 -1.52733598e-01 -4.66942413e-02]]
|
|
230
|
+
|
|
231
|
+
See :ref:`here<Averaging methods>` for further details about the averaging methods.
|
|
@@ -77,20 +77,24 @@ If we want to consider multiple orientations at once, we can create an array of
|
|
|
77
77
|
Shape=(10000,)
|
|
78
78
|
|
|
79
79
|
If we want to compute the strain for each combination of orientations and temperatures in ``[0,1,2]`` (as done above),
|
|
80
|
-
we can use the ``
|
|
80
|
+
we can use the ``apply_temperature()`` operator with ``mode='cross'``:
|
|
81
81
|
|
|
82
|
-
>>> eps = alpha_rotated.
|
|
82
|
+
>>> eps = alpha_rotated.apply_temperature([0,1,2], mode='cross')
|
|
83
83
|
>>> print(eps)
|
|
84
84
|
Strain tensor
|
|
85
85
|
Shape=(10000, 3)
|
|
86
86
|
|
|
87
|
+
.. note::
|
|
88
|
+
|
|
89
|
+
Above, we have used ``*``, which is just a shortcut for ``apply_temperature(...,mode='pair')``.
|
|
90
|
+
|
|
87
91
|
For instance we can check out the maximum value for initial (0°) and final (2°) temperatures:
|
|
88
92
|
|
|
89
93
|
>>> eps[:,0].max() # 0 because it corresponds to 0°
|
|
90
94
|
Strain tensor
|
|
91
|
-
[[
|
|
92
|
-
[
|
|
93
|
-
[
|
|
95
|
+
[[0. 0. 0.]
|
|
96
|
+
[0. 0. 0.]
|
|
97
|
+
[0. 0. 0.]]
|
|
94
98
|
>>> eps[:,-1].max()
|
|
95
99
|
Strain tensor
|
|
96
100
|
[[1.12000000e-05 5.99947076e-06 5.99905095e-06]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -32,6 +32,10 @@ Welcome to Elasticipy's Documentation!
|
|
|
32
32
|
.. image:: https://img.shields.io/pypi/pyversions/Elasticipy
|
|
33
33
|
:alt: PyPI - Python Version
|
|
34
34
|
|
|
35
|
+
.. image:: https://joss.theoj.org/papers/8cce91b782f17f52e9ee30916cd86ad5/status.svg
|
|
36
|
+
:alt: JOSS status
|
|
37
|
+
:target: https://joss.theoj.org/papers/8cce91b782f17f52e9ee30916cd86ad5
|
|
38
|
+
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
Purpose of this package
|