carapace_core/model/materials/
steel02.rs1use super::Material;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Steel02Kon {
10 Initial,
11 Positive,
12 Negative,
13}
14
15impl Material {
16 #[allow(clippy::too_many_arguments)]
21 pub fn steel02(
22 fy: f64,
23 e0: f64,
24 b: f64,
25 r0: f64,
26 cr1: f64,
27 cr2: f64,
28 a1: f64,
29 a2: f64,
30 a3: f64,
31 a4: f64,
32 ) -> Self {
33 let epsy = fy / e0;
34 Material::Steel02 {
35 fy,
36 e0,
37 b,
38 r0,
39 cr1,
40 cr2,
41 a1,
42 a2,
43 a3,
44 a4,
45 min_strain: -epsy,
46 max_strain: epsy,
47 pl_strain: 0.0,
48 asymptote_strain: 0.0,
49 asymptote_stress: 0.0,
50 reversal_strain: 0.0,
51 reversal_stress: 0.0,
52 kon: Steel02Kon::Initial,
53 strain: 0.0,
54 stress: 0.0,
55 }
56 }
57}
58
59pub(super) fn evaluate(m: &Material, strain: f64) -> (f64, f64, Material) {
70 let Material::Steel02 {
71 fy,
72 e0,
73 b,
74 r0,
75 cr1,
76 cr2,
77 a1,
78 a2,
79 a3,
80 a4,
81 min_strain,
82 max_strain,
83 pl_strain,
84 asymptote_strain,
85 asymptote_stress,
86 reversal_strain,
87 reversal_stress,
88 kon,
89 strain: cstrain,
90 stress: cstress,
91 } = m
92 else {
93 unreachable!()
94 };
95 let (fy, e0, b, r0, cr1, cr2, a1, a2, a3, a4) =
96 (*fy, *e0, *b, *r0, *cr1, *cr2, *a1, *a2, *a3, *a4);
97 let (mut min_strain, mut max_strain, mut pl_strain) = (*min_strain, *max_strain, *pl_strain);
98 let (mut asymptote_strain, mut asymptote_stress) = (*asymptote_strain, *asymptote_stress);
99 let (mut reversal_strain, mut reversal_stress) = (*reversal_strain, *reversal_stress);
100 let mut kon = *kon;
101 let (cstrain, cstress) = (*cstrain, *cstress);
102
103 let esh = b * e0;
104 let epsy = fy / e0;
105 let dstrain = strain - cstrain;
106
107 if kon == Steel02Kon::Initial {
108 max_strain = epsy;
109 min_strain = -epsy;
110 if dstrain < 0.0 {
111 kon = Steel02Kon::Negative;
112 asymptote_strain = min_strain;
113 asymptote_stress = -fy;
114 pl_strain = min_strain;
115 } else {
116 kon = Steel02Kon::Positive;
117 asymptote_strain = max_strain;
118 asymptote_stress = fy;
119 pl_strain = max_strain;
120 }
121 }
122
123 if kon == Steel02Kon::Negative && dstrain > 0.0 {
128 kon = Steel02Kon::Positive;
129 reversal_strain = cstrain;
130 reversal_stress = cstress;
131 if cstrain < min_strain {
132 min_strain = cstrain;
133 }
134 let d1 = (max_strain - min_strain) / (2.0 * (a4 * epsy));
135 let shift = 1.0 + a3 * d1.powf(0.8);
136 asymptote_strain =
137 (fy * shift - esh * epsy * shift - reversal_stress + e0 * reversal_strain) / (e0 - esh);
138 asymptote_stress = fy * shift + esh * (asymptote_strain - epsy * shift);
139 pl_strain = max_strain;
140 } else if kon == Steel02Kon::Positive && dstrain < 0.0 {
141 kon = Steel02Kon::Negative;
142 reversal_strain = cstrain;
143 reversal_stress = cstress;
144 if cstrain > max_strain {
145 max_strain = cstrain;
146 }
147 let d1 = (max_strain - min_strain) / (2.0 * (a2 * epsy));
148 let shift = 1.0 + a1 * d1.powf(0.8);
149 asymptote_strain = (-fy * shift + esh * epsy * shift - reversal_stress
150 + e0 * reversal_strain)
151 / (e0 - esh);
152 asymptote_stress = -fy * shift + esh * (asymptote_strain + epsy * shift);
153 pl_strain = min_strain;
154 }
155
156 let xi = ((pl_strain - asymptote_strain) / epsy).abs();
162 let r = r0 * (1.0 - (cr1 * xi) / (cr2 + xi));
163 let eps_ratio = (strain - reversal_strain) / (asymptote_strain - reversal_strain);
164 let dum1 = 1.0 + eps_ratio.abs().powf(r);
165 let dum2 = dum1.powf(1.0 / r);
166
167 let stress = (b * eps_ratio + (1.0 - b) * eps_ratio / dum2)
168 * (asymptote_stress - reversal_stress)
169 + reversal_stress;
170 let tangent = (b + (1.0 - b) / (dum1 * dum2)) * (asymptote_stress - reversal_stress)
171 / (asymptote_strain - reversal_strain);
172
173 (
174 stress,
175 tangent,
176 Material::Steel02 {
177 fy,
178 e0,
179 b,
180 r0,
181 cr1,
182 cr2,
183 a1,
184 a2,
185 a3,
186 a4,
187 min_strain,
188 max_strain,
189 pl_strain,
190 asymptote_strain,
191 asymptote_stress,
192 reversal_strain,
193 reversal_stress,
194 kon,
195 strain,
196 stress,
197 },
198 )
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn steel02_gives_exact_initial_tangent_at_zero_strain() {
207 let m = Material::steel02(60.0, 29000.0, 0.01, 18.5, 0.925, 0.15, 0.9, 5.0, 0.9, 5.0);
208 assert_eq!(m.trial_stress_tangent(0.0), (0.0, 29000.0));
212 }
213
214 #[test]
215 fn steel02_tangent_approaches_hardening_slope_far_past_yield() {
216 let m = Material::steel02(60.0, 29000.0, 0.01, 18.5, 0.925, 0.15, 0.9, 5.0, 0.9, 5.0);
217 let esh = 0.01 * 29000.0;
218 let (_stress, tangent) = m.trial_stress_tangent(0.05);
222 assert!(
223 (tangent - esh).abs() < 1.0,
224 "expected tangent near Esh={esh}, got {tangent}"
225 );
226 }
227
228 #[test]
229 fn steel02_reversal_starts_out_near_elastic_stiffness() {
230 let m = Material::steel02(60.0, 29000.0, 0.01, 18.5, 0.925, 0.15, 0.9, 5.0, 0.9, 5.0);
231 let m = m.commit(0.02);
236 let (_stress, tangent) = m.trial_stress_tangent(0.0199);
237 assert!(
238 tangent > 0.9 * 29000.0,
239 "expected near-elastic unloading tangent, got {tangent}"
240 );
241 }
242
243 #[test]
244 fn steel02_tracks_peak_strain_across_a_reversal() {
245 let m = Material::steel02(60.0, 29000.0, 0.01, 18.5, 0.925, 0.15, 0.9, 5.0, 0.9, 5.0);
246 let m = m.commit(0.02);
247 let m = m.commit(-0.01);
248 let Material::Steel02 { max_strain, .. } = m else {
249 panic!()
250 };
251 assert!((max_strain - 0.02).abs() < 1e-9, "got {max_strain}");
254 }
255}