Chaboche#

A single Armstrong-Frederick back-stress is one exponential, saturating at \(2C/3\gamma\) with a single rate. A measured hysteresis loop has both a sharp knee after yield and a long, nearly linear tail, and no single exponential fits both.

Chaboche superposes several, \(X = \sum_i X_i\): a fast component for the knee, an intermediate one for the curvature, and a linear one (\(\gamma = 0\)) for the tail.

Reference#

J.-L. Chaboche, Time-independent constitutive theories for cyclic plasticity, Int. J. Plasticity 2 (1986) 149–188.

  • One exponential cannot follow both the knee and the tail
  • The superposition: fast, intermediate and linear ($\gamma$ = 0)
Chaboche with one component vs ArmstrongFrederick: 0.0e+00 MPa
  X1 peaks at   79.76 of its   80.00 bound
  X2 peaks at   83.93 of its  133.33 bound
  X3 is linear in the plastic strain to 0.00e+00 MPa
  at the peak, |sigma| = 509.73 = sigma_y + 3/2 |X| = 509.73

 25 from enum import Enum
 26
 27 import numpy as np
 28
 29 from EasyFEA import Matplotlib, Models
 30 from EasyFEA.Models.Elastic._laws import Isotropic
 31
 32 # ----------------------------------------------
 33 # Material
 34 # ----------------------------------------------
 35 E, v = 210000.0, 0.3  # MPa
 36 sigma_y = 250.0  # MPa
 37 elastic = Isotropic(3, E=E, v=v)
 38 eps_y = sigma_y / E
 39
 40 KH = Models.InElastic.KinematicHardening
 41 MP = Models.InElastic.MaterialPoint
 42
 43 # three components: fast knee, intermediate curvature, linear tail
 44 components = [
 45     (60000.0, 500.0),
 46     (20000.0, 100.0),
 47     (2000.0, 0.0),
 48 ]
 49
 50
 51 class Laws(str, Enum):
 52     ArmstrongFrederick = "single Armstrong-Frederick"
 53     Chaboche = "Chaboche, 3 components"
 54
 55     def __str__(self):
 56         return self.name
 57
 58
 59 laws = {
 60     Laws.ArmstrongFrederick: KH.ArmstrongFrederick(*components[0]),
 61     Laws.Chaboche: KH.Chaboche(*components),
 62 }
 63
 64
 65 def Behaviour(kinematic):
 66     return Models.InElastic.Behavior(
 67         3,
 68         elastic,
 69         yieldSurface=Models.InElastic.Yield.VonMises(sigma_y),
 70         kinematic=kinematic,
 71     )
 72
 73
 74 # ----------------------------------------------
 75 # The loop shape: knee and tail
 76 # ----------------------------------------------
 77 peak = 8 * eps_y
 78 quarter = np.linspace(0.0, peak, 20)
 79 path = np.concatenate(
 80     [
 81         quarter,
 82         np.linspace(peak, -peak, 40)[1:],
 83         np.linspace(-peak, peak, 40)[1:],
 84     ]
 85 )
 86
 87 ax = Matplotlib.Init_Axes()
 88 for label, kinematic in laws.items():
 89     res = MP(Behaviour(kinematic)).Run(strain={"xx": path})
 90     ax.plot(res["strain"][:, 0] * 100, res["stress"][:, 0], lw=1.2, label=label)
 91
 92 # a superposition of one term is that term: the machinery must add nothing of its own
 93 C0, g0 = components[0]
 94 one = MP(Behaviour(KH.Chaboche((C0, g0)))).Run(strain={"xx": path})
 95 alone = MP(Behaviour(KH.ArmstrongFrederick(C0, g0))).Run(strain={"xx": path})
 96 same = np.max(np.abs(one["stress"][:, 0] - alone["stress"][:, 0]))
 97 print(f"Chaboche with one component vs ArmstrongFrederick: {same:.1e} MPa")
 98 assert same == 0.0, "the superposition is not exact for a single component"
 99
100 ax.set_xlabel("axial strain [%]")
101 ax.set_ylabel(r"$\sigma_{xx}$ [MPa]")
102 ax.set_title("One exponential cannot follow both the knee and the tail")
103 ax.legend(fontsize=8)
104 ax.grid(alpha=0.3)
105
106 # ----------------------------------------------
107 # The components that make it up
108 # ----------------------------------------------
109 behaviour = Behaviour(KH.Chaboche(*components))
110 res = MP(behaviour).Run(strain={"xx": path})
111
112 ax = Matplotlib.Init_Axes()
113 total = np.zeros_like(res["strain"][:, 0])
114 for i, (C, gamma) in enumerate(components):
115     X_xx = 2 / 3 * C * res[f"alpha{i}"][:, 0]
116     total = total + X_xx
117     ax.plot(
118         res["strain"][:, 0] * 100,
119         X_xx,
120         lw=1,
121         label=rf"$X_{i + 1}$: $C$={C:.0f}, $\gamma$={gamma:.0f}",
122     )
123     if gamma > 0:
124         # the recall term bounds each component; the fast one all but reaches its bound
125         bound = 2 * C / (3 * gamma)
126         print(
127             f"  X{i + 1} peaks at {np.abs(X_xx).max():7.2f} of its {bound:7.2f} bound"
128         )
129         assert np.abs(X_xx).max() <= bound * (1 + 1e-9), f"X{i + 1} passed 2C/3gamma"
130     else:
131         # with no recall alpha is just the plastic strain, so this component is linear in it
132         linear = np.max(np.abs(X_xx - 2 / 3 * C * res["eps_p"][:, 0]))
133         print(f"  X{i + 1} is linear in the plastic strain to {linear:.2e} MPa")
134         assert linear < 1e-9, "the gamma = 0 component is not linear"
135
136 ax.plot(res["strain"][:, 0] * 100, total, "k-", lw=1.4, label=r"$X = \sum_i X_i$")
137
138 # the yield condition, read off the peak: the axial equivalent of X is 3/2 its xx component
139 k = int(np.argmax(np.abs(res["stress"][:, 0])))
140 print(
141     f"  at the peak, |sigma| = {abs(res['stress'][k, 0]):.2f} "
142     f"= sigma_y + 3/2 |X| = {sigma_y + 1.5 * abs(total[k]):.2f}"
143 )
144 assert abs(abs(res["stress"][k, 0]) - sigma_y - 1.5 * abs(total[k])) < 1e-6
145 ax.set_xlabel("axial strain [%]")
146 ax.set_ylabel(r"$X_{xx}$ [MPa]")
147 ax.set_title(r"The superposition: fast, intermediate and linear ($\gamma$ = 0)")
148 ax.legend(fontsize=8)
149 ax.grid(alpha=0.3)
150
151 Matplotlib.plt.show()

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

Gallery generated by Sphinx-Gallery