StressStrain#

Uniaxial curves for the shipped hardening laws, against their closed forms.

MaterialPoint drives one Gauss point with no mesh and no solver, holding the un-driven components stress-free. Imposing \(\varepsilon_{xx}\) alone is therefore uniaxial tension, and every curve has an exact solution:

\[\sigma = E(\varepsilon - p) \quad\text{with}\quad \sigma = \sigma_y + R(p)\]

one scalar root per point. R is written out below from the definitions rather than taken from the framework, so the agreement is a check and not a tautology.

  • Isotropic hardening laws, uniaxial tension
  • Voce hardening on three different surfaces
Perfect                      max |error| = 3.77e-14 of sigma_y
Linear                       max |error| = 3.12e-14 of sigma_y
Voce                         max |error| = 2.15e-13 of sigma_y
Swift                        max |error| = 3.88e-12 of sigma_y

worst over every law: 3.88e-12 of sigma_y

Hill yields uniaxially at 204.12 MPa, in [188.44, 204.43]

Every curve above is the same engine: only the pieces handed to it differ.

 25 from enum import Enum
 26
 27 import numpy as np
 28 from scipy.optimize import brentq
 29
 30 from EasyFEA import Matplotlib, Models
 31 from EasyFEA.Models.Elastic._laws import Isotropic
 32
 33 # ----------------------------------------------
 34 # Configuration
 35 # ----------------------------------------------
 36 E, v = 210000.0, 0.3  # MPa
 37 sigma_y = 250.0  # MPa
 38
 39 elastic = Isotropic(3, E=E, v=v)
 40 eps_y = sigma_y / E
 41
 42 path = np.linspace(0.0, 30 * eps_y, 200)
 43
 44 H = 2000.0  # linear
 45 Q, b = 150.0, 30.0  # Voce
 46 K, n, eps0 = 600.0, 0.2, 1e-4  # Swift, with the default pre-strain
 47
 48
 49 def Exact(eps: np.ndarray, R) -> np.ndarray:
 50     """Uniaxial tension: sigma = E (eps - p), with p from sigma = sigma_y + R(p)."""
 51     out = np.where(E * eps <= sigma_y, E * eps, 0.0)
 52     for i, e in enumerate(eps):
 53         if E * e > sigma_y:
 54             # xtol tightened: at the default, brentq's own root error times E is the floor
 55             p = brentq(lambda p: E * (e - p) - sigma_y - R(p), 0.0, e, xtol=1e-15)
 56             out[i] = E * (e - p)
 57     return out
 58
 59
 60 # ----------------------------------------------
 61 # One curve per hardening law
 62 # ----------------------------------------------
 63 class Hardenings(str, Enum):
 64     Perfect = "perfect"
 65     Linear = f"linear, H = {H:.0f}"
 66     Voce = f"Voce, Q = {Q:.0f}, b = {b:.0f}"
 67     Swift = f"Swift, K = {K:.0f}, n = {n}"
 68
 69     def __str__(self):
 70         return self.name
 71
 72
 73 hardenings = {
 74     Hardenings.Perfect: (None, lambda p: 0.0),
 75     Hardenings.Linear: (Models.InElastic.IsotropicHardening.Linear(H), lambda p: H * p),
 76     Hardenings.Voce: (
 77         Models.InElastic.IsotropicHardening.Voce(Q, b),
 78         lambda p: Q * (1 - np.exp(-b * p)),
 79     ),
 80     Hardenings.Swift: (
 81         Models.InElastic.IsotropicHardening.Swift(K, n),
 82         lambda p: K * ((eps0 + p) ** n - eps0**n),
 83     ),
 84 }
 85
 86 ax = Matplotlib.Init_Axes()
 87 worst = 0.0
 88 for i, (label, (hardening, R)) in enumerate(hardenings.items()):
 89     law = Models.InElastic.Behavior(
 90         3,
 91         elastic,
 92         hardening=hardening,
 93         yieldSurface=Models.InElastic.Yield.VonMises(sigma_y),
 94     )
 95     res = Models.InElastic.MaterialPoint(law).Run(strain={"xx": path})
 96     eps, sig = res["strain"][:, 0], res["stress"][:, 0]
 97
 98     err = np.max(np.abs(sig - Exact(eps, R))) / sigma_y
 99     worst = max(worst, err)
100     print(f"{label:28s} max |error| = {err:.2e} of sigma_y")
101
102     ax.plot(eps * 100, sig, lw=1.4, label=label.value)
103     ax.plot(
104         eps * 100,
105         Exact(eps, R),
106         "k--",
107         lw=0.8,
108         label="closed form" if i == 0 else None,
109     )
110
111 print(f"\nworst over every law: {worst:.2e} of sigma_y")
112 assert worst < 1e-10, "a hardening law does not reproduce its own closed form"
113
114 ax.axhline(sigma_y, ls=":", c="k", lw=0.8)
115 ax.text(path[-1] * 100, sigma_y, r"$\sigma_y$ ", ha="right", va="top")
116 ax.set_xlabel("axial strain [%]")
117 ax.set_ylabel(r"$\sigma_{xx}$ [MPa]")
118 ax.set_title("Isotropic hardening laws, uniaxial tension")
119 ax.legend(loc="lower right")
120 ax.grid(alpha=0.3)
121
122 # ----------------------------------------------
123 # The same hardening on a different surface
124 # ----------------------------------------------
125 # G + H must not be 1, or Hill reduces to von Mises along x and the two curves coincide
126 F, G, Hh, Lh, M, N = 0.7, 0.6, 0.9, 1.8, 1.2, 1.4
127 hill_y = sigma_y / np.sqrt(G + Hh)
128
129
130 class Surfaces(str, Enum):
131     VonMises = "von Mises"
132     DruckerPrager = r"Drucker-Prager $\eta$ = 0.2"
133     Hill = "Hill (anisotropic)"
134
135     def __str__(self):
136         return self.name
137
138
139 surfaces = {
140     Surfaces.VonMises: Models.InElastic.Yield.VonMises(sigma_y),
141     Surfaces.DruckerPrager: Models.InElastic.Yield.DruckerPrager(sigma_y, 0.2),
142     Surfaces.Hill: Models.InElastic.Yield.Hill(sigma_y, F=F, G=G, H=Hh, L=Lh, M=M, N=N),
143 }
144
145 ax = Matplotlib.Init_Axes()
146 for label, surface in surfaces.items():
147     law = Models.InElastic.Behavior(
148         3,
149         elastic,
150         hardening=Models.InElastic.IsotropicHardening.Voce(Q, b),
151         yieldSurface=surface,
152     )
153     res = Models.InElastic.MaterialPoint(law).Run(strain={"xx": path})
154     ax.plot(res["strain"][:, 0] * 100, res["stress"][:, 0], label=label.value)
155
156     if label is Surfaces.Hill:
157         # uniaxially Hill reduces to sigma_xx sqrt(G + H), so first yield is bracketed
158         onset = int(np.argmax(np.asarray(res["p"]) > 0))
159         sig = res["stress"][:, 0]
160         print(
161             f"\nHill yields uniaxially at {hill_y:.2f} MPa, in [{sig[onset - 1]:.2f}, {sig[onset]:.2f}]"
162         )
163         assert sig[onset - 1] <= hill_y <= sig[onset], "Hill's uniaxial yield is wrong"
164
165 # annotated at the right edge, where these two levels sit below every curve
166 right = path[-1] * 100
167 ax.axhline(hill_y, ls=":", c="k", lw=0.8)
168 ax.text(right, hill_y, r"Hill: $\sigma_y/\sqrt{G+H}$ ", ha="right", va="top")
169 ax.axhline(sigma_y, ls=":", c="k", lw=0.8)
170 ax.text(right, sigma_y, r"von Mises: $\sigma_y$ ", ha="right", va="top")
171 ax.set_xlabel("axial strain [%]")
172 ax.set_ylabel(r"$\sigma_{xx}$ [MPa]")
173 ax.set_title("Voce hardening on three different surfaces")
174 ax.legend(loc="lower right")
175 ax.grid(alpha=0.3)
176
177 print("\nEvery curve above is the same engine: only the pieces handed to it differ.")
178
179 Matplotlib.plt.show()

Total running time of the script: (0 minutes 8.338 seconds)

Gallery generated by Sphinx-Gallery