Note
Go to the end to download the full example code.
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.
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)

