1use super::Material;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Pinching4State {
23 NearOrigin,
24 Positive,
25 Negative,
26 Trilinear3,
27 Trilinear4,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum Pinching4DmgCyc {
39 EnergyBased,
40 CycleBased,
41}
42
43#[derive(Debug, Clone, Copy)]
81pub struct Pinching4Fields {
82 pub envlp_pos_strain: [f64; 6],
84 pub envlp_pos_stress: [f64; 6],
85 pub envlp_neg_strain: [f64; 6],
86 pub envlp_neg_stress: [f64; 6],
87 pub k_elastic_pos: f64,
88 pub k_elastic_neg: f64,
89 pub energy_capacity: f64,
90 pub r_disp_p: f64,
92 pub r_force_p: f64,
93 pub u_force_p: f64,
94 pub r_disp_n: f64,
95 pub r_force_n: f64,
96 pub u_force_n: f64,
97 pub gamma_k_params: [f64; 4],
99 pub gamma_k_limit: f64,
100 pub gamma_d_params: [f64; 4],
101 pub gamma_d_limit: f64,
102 pub gamma_f_params: [f64; 4],
103 pub gamma_f_limit: f64,
104 pub dmg_cyc: Pinching4DmgCyc,
105 pub state: Pinching4State,
107 pub strain: f64,
108 pub stress: f64,
109 pub strain_rate: f64,
110 pub low_state_strain: f64,
111 pub low_state_stress: f64,
112 pub hgh_state_strain: f64,
113 pub hgh_state_stress: f64,
114 pub min_strain_dmnd: f64,
115 pub max_strain_dmnd: f64,
116 pub energy: f64,
117 pub gamma_k: f64,
118 pub gamma_d: f64,
119 pub gamma_f: f64,
120 pub gamma_k_used: f64,
121 pub gamma_f_used: f64,
122 pub n_cycle: f64,
123}
124
125struct Trial {
138 state: Pinching4State,
139 low_strain: f64,
140 low_stress: f64,
141 hgh_strain: f64,
142 hgh_stress: f64,
143 min_dmnd: f64,
144 max_dmnd: f64,
145 energy: f64,
146 gamma_k: f64,
147 gamma_d: f64,
148 gamma_f: f64,
149 gamma_k_used: f64,
150 gamma_f_used: f64,
151 k_elastic_pos_damgd: f64,
152 k_elastic_neg_damgd: f64,
153 pos_damgd_stress: [f64; 6],
154 neg_damgd_stress: [f64; 6],
155 n_cycle: f64,
156}
157
158impl Trial {
159 fn pos_envlp_stress(&self, p: &Pinching4Fields, u: f64) -> f64 {
164 let mut k = 0.0;
165 let mut f = 0.0;
166 let mut i = 0;
167 while k == 0.0 && i <= 4 {
168 if u <= p.envlp_pos_strain[i + 1] {
169 k = (self.pos_damgd_stress[i + 1] - self.pos_damgd_stress[i])
170 / (p.envlp_pos_strain[i + 1] - p.envlp_pos_strain[i]);
171 f = self.pos_damgd_stress[i] + (u - p.envlp_pos_strain[i]) * k;
172 }
173 i += 1;
174 }
175 if k == 0.0 {
176 k = (self.pos_damgd_stress[5] - self.pos_damgd_stress[4])
177 / (p.envlp_pos_strain[5] - p.envlp_pos_strain[4]);
178 f = self.pos_damgd_stress[5] + k * (u - p.envlp_pos_strain[5]);
179 }
180 f
181 }
182
183 fn pos_envlp_tangent(&self, p: &Pinching4Fields, u: f64) -> f64 {
185 let mut k = 0.0;
186 let mut i = 0;
187 while k == 0.0 && i <= 4 {
188 if u <= p.envlp_pos_strain[i + 1] {
189 k = (self.pos_damgd_stress[i + 1] - self.pos_damgd_stress[i])
190 / (p.envlp_pos_strain[i + 1] - p.envlp_pos_strain[i]);
191 }
192 i += 1;
193 }
194 if k == 0.0 {
195 k = (self.pos_damgd_stress[5] - self.pos_damgd_stress[4])
196 / (p.envlp_pos_strain[5] - p.envlp_pos_strain[4]);
197 }
198 k
199 }
200
201 fn neg_envlp_stress(&self, p: &Pinching4Fields, u: f64) -> f64 {
203 let mut k = 0.0;
204 let mut f = 0.0;
205 let mut i = 0;
206 while k == 0.0 && i <= 4 {
207 if u >= p.envlp_neg_strain[i + 1] {
208 k = (self.neg_damgd_stress[i] - self.neg_damgd_stress[i + 1])
209 / (p.envlp_neg_strain[i] - p.envlp_neg_strain[i + 1]);
210 f = self.neg_damgd_stress[i + 1] + (u - p.envlp_neg_strain[i + 1]) * k;
211 }
212 i += 1;
213 }
214 if k == 0.0 {
215 k = (self.neg_damgd_stress[4] - self.neg_damgd_stress[5])
216 / (p.envlp_neg_strain[4] - p.envlp_neg_strain[5]);
217 f = self.neg_damgd_stress[5] + k * (u - p.envlp_neg_strain[5]);
218 }
219 f
220 }
221
222 fn neg_envlp_tangent(&self, p: &Pinching4Fields, u: f64) -> f64 {
224 let mut k = 0.0;
225 let mut i = 0;
226 while k == 0.0 && i <= 4 {
227 if u >= p.envlp_neg_strain[i + 1] {
228 k = (self.neg_damgd_stress[i] - self.neg_damgd_stress[i + 1])
229 / (p.envlp_neg_strain[i] - p.envlp_neg_strain[i + 1]);
230 }
231 i += 1;
232 }
233 if k == 0.0 {
234 k = (self.neg_damgd_stress[4] - self.neg_damgd_stress[5])
235 / (p.envlp_neg_strain[4] - p.envlp_neg_strain[5]);
236 }
237 k
238 }
239}
240
241impl Material {
242 #[allow(clippy::too_many_arguments)]
258 pub fn pinching4(
259 stress1p: f64,
260 strain1p: f64,
261 stress2p: f64,
262 strain2p: f64,
263 stress3p: f64,
264 strain3p: f64,
265 stress4p: f64,
266 strain4p: f64,
267 stress1n: f64,
268 strain1n: f64,
269 stress2n: f64,
270 strain2n: f64,
271 stress3n: f64,
272 strain3n: f64,
273 stress4n: f64,
274 strain4n: f64,
275 r_disp_p: f64,
276 r_force_p: f64,
277 u_force_p: f64,
278 r_disp_n: f64,
279 r_force_n: f64,
280 u_force_n: f64,
281 gamma_k_params: [f64; 4],
282 gamma_k_limit: f64,
283 gamma_d_params: [f64; 4],
284 gamma_d_limit: f64,
285 gamma_f_params: [f64; 4],
286 gamma_f_limit: f64,
287 gamma_e: f64,
288 dmg_cyc: Pinching4DmgCyc,
289 ) -> Self {
290 debug_assert!(
291 strain1p > 0.0 && strain2p > 0.0 && strain3p > 0.0 && strain4p > 0.0,
292 "positive backbone strains must all be positive"
293 );
294 debug_assert!(
295 strain1n < 0.0 && strain2n < 0.0 && strain3n < 0.0 && strain4n < 0.0,
296 "negative backbone strains must all be negative"
297 );
298
299 let k_pos = stress1p / strain1p;
301 let k_neg = stress1n / strain1n;
302 let k = if k_pos > k_neg { k_pos } else { k_neg };
303 let u = if strain1p > -strain1n {
304 1e-4 * strain1p
305 } else {
306 -1e-4 * strain1n
307 };
308
309 let mut envlp_pos_strain = [u, strain1p, strain2p, strain3p, strain4p, 0.0];
310 let mut envlp_pos_stress = [u * k, stress1p, stress2p, stress3p, stress4p, 0.0];
311 let mut envlp_neg_strain = [-u, strain1n, strain2n, strain3n, strain4n, 0.0];
312 let mut envlp_neg_stress = [-u * k, stress1n, stress2n, stress3n, stress4n, 0.0];
313
314 let k1 = (stress4p - stress3p) / (strain4p - strain3p);
315 let k2 = (stress4n - stress3n) / (strain4n - strain3n);
316
317 envlp_pos_strain[5] = 1e6 * strain4p;
318 envlp_pos_stress[5] = if k1 > 0.0 {
319 stress4p + k1 * (envlp_pos_strain[5] - strain4p)
320 } else {
321 stress4p * 1.1
322 };
323 envlp_neg_strain[5] = 1e6 * strain4n;
324 envlp_neg_stress[5] = if k2 > 0.0 {
325 stress4n + k2 * (envlp_neg_strain[5] - strain4n)
326 } else {
327 stress4n * 1.1
328 };
329
330 let k_elastic_pos = envlp_pos_stress[1] / envlp_pos_strain[1];
331 let k_elastic_neg = envlp_neg_stress[1] / envlp_neg_strain[1];
332
333 let mut energy_pos = 0.5 * envlp_pos_strain[0] * envlp_pos_stress[0];
334 for jt in 0..4 {
335 energy_pos += 0.5
336 * (envlp_pos_stress[jt] + envlp_pos_stress[jt + 1])
337 * (envlp_pos_strain[jt + 1] - envlp_pos_strain[jt]);
338 }
339 let mut energy_neg = 0.5 * envlp_neg_strain[0] * envlp_neg_stress[0];
340 for jy in 0..4 {
341 energy_neg += 0.5
342 * (envlp_neg_stress[jy] + envlp_neg_stress[jy + 1])
343 * (envlp_neg_strain[jy + 1] - envlp_neg_strain[jy]);
344 }
345 let max_energy = if energy_pos > energy_neg {
346 energy_pos
347 } else {
348 energy_neg
349 };
350 let energy_capacity = gamma_e * max_energy;
351
352 Material::Pinching4(Box::new(Pinching4Fields {
354 envlp_pos_strain,
355 envlp_pos_stress,
356 envlp_neg_strain,
357 envlp_neg_stress,
358 k_elastic_pos,
359 k_elastic_neg,
360 energy_capacity,
361 r_disp_p,
362 r_force_p,
363 u_force_p,
364 r_disp_n,
365 r_force_n,
366 u_force_n,
367 gamma_k_params,
368 gamma_k_limit,
369 gamma_d_params,
370 gamma_d_limit,
371 gamma_f_params,
372 gamma_f_limit,
373 dmg_cyc,
374 state: Pinching4State::NearOrigin,
375 strain: 0.0,
376 stress: 0.0,
377 strain_rate: 0.0,
378 low_state_strain: envlp_neg_strain[0],
379 low_state_stress: envlp_neg_stress[0],
380 hgh_state_strain: envlp_pos_strain[0],
381 hgh_state_stress: envlp_pos_stress[0],
382 min_strain_dmnd: envlp_neg_strain[1],
383 max_strain_dmnd: envlp_pos_strain[1],
384 energy: 0.0,
385 gamma_k: 0.0,
386 gamma_d: 0.0,
387 gamma_f: 0.0,
388 gamma_k_used: 0.0,
389 gamma_f_used: 0.0,
390 n_cycle: 0.0,
391 }))
392 }
393}
394
395pub(super) fn evaluate(m: &Material, strain: f64) -> (f64, f64, Material) {
404 let Material::Pinching4(fields) = m else {
405 unreachable!()
406 };
407 let p = **fields;
408
409 let mut dstrain = strain - p.strain;
410 if dstrain < 1e-12 && dstrain > -1e-12 {
411 dstrain = 0.0;
412 }
413
414 let u_max_damgd = p.max_strain_dmnd * (1.0 + p.gamma_d);
418 let u_min_damgd = p.min_strain_dmnd * (1.0 + p.gamma_d);
419
420 let mut t = Trial {
421 state: p.state,
422 low_strain: p.low_state_strain,
423 low_stress: p.low_state_stress,
424 hgh_strain: p.hgh_state_strain,
425 hgh_stress: p.hgh_state_stress,
426 min_dmnd: p.min_strain_dmnd,
427 max_dmnd: p.max_strain_dmnd,
428 energy: p.energy,
429 gamma_k: p.gamma_k,
430 gamma_d: p.gamma_d,
431 gamma_f: p.gamma_f,
432 gamma_k_used: p.gamma_k_used,
433 gamma_f_used: p.gamma_f_used,
434 k_elastic_pos_damgd: p.k_elastic_pos * (1.0 - p.gamma_k_used),
435 k_elastic_neg_damgd: p.k_elastic_neg * (1.0 - p.gamma_k_used),
436 pos_damgd_stress: scaled(&p.envlp_pos_stress, 1.0 - p.gamma_f_used),
437 neg_damgd_stress: scaled(&p.envlp_neg_stress, 1.0 - p.gamma_f_used),
438 n_cycle: p.n_cycle,
439 };
440
441 getstate(&mut t, &p, strain, dstrain, u_max_damgd, u_min_damgd);
442
443 let (stress, tangent) = match t.state {
444 Pinching4State::NearOrigin => {
445 let tangent = p.envlp_pos_stress[0] / p.envlp_pos_strain[0];
446 (tangent * strain, tangent)
447 }
448 Pinching4State::Positive => (
449 t.pos_envlp_stress(&p, strain),
450 t.pos_envlp_tangent(&p, strain),
451 ),
452 Pinching4State::Negative => (
453 t.neg_envlp_stress(&p, strain),
454 t.neg_envlp_tangent(&p, strain),
455 ),
456 Pinching4State::Trilinear3 => {
457 let kunload = if t.hgh_strain < 0.0 {
458 t.k_elastic_neg_damgd
459 } else {
460 t.k_elastic_pos_damgd
461 };
462 let mut s3_strain = [t.low_strain, 0.0, 0.0, t.hgh_strain];
463 let mut s3_stress = [t.low_stress, 0.0, 0.0, t.hgh_stress];
464 get_state3(&mut s3_strain, &mut s3_stress, kunload, &t, &p);
465 (
466 envlp34_stress(&s3_strain, &s3_stress, strain),
467 envlp34_tangent(&s3_strain, &s3_stress, strain),
468 )
469 }
470 Pinching4State::Trilinear4 => {
471 let kunload = if t.low_strain < 0.0 {
472 t.k_elastic_neg_damgd
473 } else {
474 t.k_elastic_pos_damgd
475 };
476 let mut s4_strain = [t.low_strain, 0.0, 0.0, t.hgh_strain];
477 let mut s4_stress = [t.low_stress, 0.0, 0.0, t.hgh_stress];
478 get_state4(&mut s4_strain, &mut s4_stress, kunload, &t, &p);
479 (
480 envlp34_stress(&s4_strain, &s4_stress, strain),
481 envlp34_tangent(&s4_strain, &s4_stress, strain),
482 )
483 }
484 };
485
486 let denergy = 0.5 * (stress + p.stress) * dstrain;
487 let elastic_strain_energy = if strain > 0.0 {
488 0.5 * stress / t.k_elastic_pos_damgd * stress
489 } else {
490 0.5 * stress / t.k_elastic_neg_damgd * stress
491 };
492 t.energy = p.energy + denergy;
493
494 update_dmg(
495 &mut t,
496 &p,
497 strain,
498 dstrain,
499 elastic_strain_energy,
500 p.n_cycle,
501 );
502
503 let strain_rate = if dstrain > 1e-12 || dstrain < -1e-12 {
506 dstrain
507 } else {
508 p.strain_rate
509 };
510
511 (
512 stress,
513 tangent,
514 Material::Pinching4(Box::new(Pinching4Fields {
515 state: t.state,
516 strain,
517 stress,
518 strain_rate,
519 low_state_strain: t.low_strain,
520 low_state_stress: t.low_stress,
521 hgh_state_strain: t.hgh_strain,
522 hgh_state_stress: t.hgh_stress,
523 min_strain_dmnd: t.min_dmnd,
524 max_strain_dmnd: t.max_dmnd,
525 energy: t.energy,
526 gamma_k: t.gamma_k,
527 gamma_d: t.gamma_d,
528 gamma_f: t.gamma_f,
529 gamma_k_used: t.gamma_k_used,
530 gamma_f_used: t.gamma_f_used,
531 n_cycle: t.n_cycle,
532 ..p
533 })),
534 )
535}
536
537fn scaled(v: &[f64; 6], factor: f64) -> [f64; 6] {
538 let mut out = [0.0; 6];
539 for i in 0..6 {
540 out[i] = v[i] * factor;
541 }
542 out
543}
544
545fn getstate(
554 t: &mut Trial,
555 p: &Pinching4Fields,
556 u: f64,
557 du: f64,
558 u_max_damgd: f64,
559 u_min_damgd: f64,
560) {
561 let c_strain = p.strain;
564 let c_stress = p.stress;
565
566 let cid = du * p.strain_rate <= 0.0;
567 let mut cis = false;
568 let mut new_state = Pinching4State::NearOrigin;
569
570 if u < t.low_strain || u > t.hgh_strain || cid {
571 match t.state {
572 Pinching4State::NearOrigin => {
573 if u > t.hgh_strain {
574 cis = true;
575 new_state = Pinching4State::Positive;
576 t.low_strain = p.envlp_pos_strain[0];
577 t.low_stress = p.envlp_pos_stress[0];
578 t.hgh_strain = p.envlp_pos_strain[5];
579 t.hgh_stress = p.envlp_pos_stress[5];
580 } else if u < t.low_strain {
581 cis = true;
582 new_state = Pinching4State::Negative;
583 t.low_strain = p.envlp_neg_strain[5];
584 t.low_stress = p.envlp_neg_stress[5];
585 t.hgh_strain = p.envlp_neg_strain[0];
586 t.hgh_stress = p.envlp_neg_stress[0];
587 }
588 }
589 Pinching4State::Positive if du < 0.0 => {
590 cis = true;
591 if c_strain > t.max_dmnd {
592 t.max_dmnd = u - du;
593 }
594 if t.max_dmnd < u_max_damgd {
595 t.max_dmnd = u_max_damgd;
596 }
597 if u < u_min_damgd {
598 new_state = Pinching4State::Negative;
599 t.gamma_f_used = p.gamma_f;
600 t.neg_damgd_stress = scaled(&p.envlp_neg_stress, 1.0 - t.gamma_f_used);
601 t.low_strain = p.envlp_neg_strain[5];
602 t.low_stress = p.envlp_neg_stress[5];
603 t.hgh_strain = p.envlp_neg_strain[0];
604 t.hgh_stress = p.envlp_neg_stress[0];
605 } else {
606 new_state = Pinching4State::Trilinear3;
607 t.low_strain = u_min_damgd;
608 t.gamma_f_used = p.gamma_f;
609 t.neg_damgd_stress = scaled(&p.envlp_neg_stress, 1.0 - t.gamma_f_used);
610 t.low_stress = t.neg_envlp_stress(p, u_min_damgd);
611 t.hgh_strain = c_strain;
612 t.hgh_stress = c_stress;
613 }
614 t.gamma_k_used = p.gamma_k;
615 t.k_elastic_pos_damgd = p.k_elastic_pos * (1.0 - t.gamma_k_used);
616 }
617 Pinching4State::Negative if du > 0.0 => {
618 cis = true;
619 if c_strain < t.min_dmnd {
620 t.min_dmnd = c_strain;
621 }
622 if t.min_dmnd > u_min_damgd {
623 t.min_dmnd = u_min_damgd;
624 }
625 if u > u_max_damgd {
626 new_state = Pinching4State::Positive;
627 t.gamma_f_used = p.gamma_f;
628 t.pos_damgd_stress = scaled(&p.envlp_pos_stress, 1.0 - t.gamma_f_used);
629 t.low_strain = p.envlp_pos_strain[0];
630 t.low_stress = p.envlp_pos_stress[0];
631 t.hgh_strain = p.envlp_pos_strain[5];
632 t.hgh_stress = p.envlp_pos_stress[5];
633 } else {
634 new_state = Pinching4State::Trilinear4;
635 t.low_strain = c_strain;
636 t.low_stress = c_stress;
637 t.hgh_strain = u_max_damgd;
638 t.gamma_f_used = p.gamma_f;
639 t.pos_damgd_stress = scaled(&p.envlp_pos_stress, 1.0 - t.gamma_f_used);
640 t.hgh_stress = t.pos_envlp_stress(p, u_max_damgd);
641 }
642 t.gamma_k_used = p.gamma_k;
643 t.k_elastic_neg_damgd = p.k_elastic_neg * (1.0 - t.gamma_k_used);
644 }
645 Pinching4State::Trilinear3 => {
646 if u < t.low_strain {
647 cis = true;
648 new_state = Pinching4State::Negative;
649 t.low_strain = p.envlp_neg_strain[5];
650 t.hgh_strain = p.envlp_neg_strain[0];
651 t.low_stress = t.neg_damgd_stress[5];
652 t.hgh_stress = t.neg_damgd_stress[0];
653 } else if u > u_max_damgd && du > 0.0 {
654 cis = true;
655 new_state = Pinching4State::Positive;
656 t.low_strain = p.envlp_pos_strain[0];
657 t.low_stress = p.envlp_pos_stress[0];
658 t.hgh_strain = p.envlp_pos_strain[5];
659 t.hgh_stress = p.envlp_pos_stress[5];
660 } else if du > 0.0 {
661 cis = true;
662 new_state = Pinching4State::Trilinear4;
663 t.low_strain = c_strain;
664 t.low_stress = c_stress;
665 t.hgh_strain = u_max_damgd;
666 t.gamma_f_used = p.gamma_f;
667 t.pos_damgd_stress = scaled(&p.envlp_pos_stress, 1.0 - t.gamma_f_used);
668 t.hgh_stress = t.pos_envlp_stress(p, u_max_damgd);
669 t.gamma_k_used = p.gamma_k;
670 t.k_elastic_neg_damgd = p.k_elastic_neg * (1.0 - t.gamma_k_used);
671 }
672 }
673 Pinching4State::Trilinear4 => {
674 if u > t.hgh_strain {
675 cis = true;
676 new_state = Pinching4State::Positive;
677 t.low_strain = p.envlp_pos_strain[0];
678 t.low_stress = t.pos_damgd_stress[0];
679 t.hgh_strain = p.envlp_pos_strain[5];
680 t.hgh_stress = t.pos_damgd_stress[5];
681 } else if u < u_min_damgd && du < 0.0 {
682 cis = true;
683 new_state = Pinching4State::Negative;
684 t.low_strain = p.envlp_neg_strain[5];
685 t.low_stress = t.neg_damgd_stress[5];
686 t.hgh_strain = p.envlp_neg_strain[0];
687 t.hgh_stress = t.neg_damgd_stress[0];
688 } else if du < 0.0 {
689 cis = true;
690 new_state = Pinching4State::Trilinear3;
691 t.low_strain = u_min_damgd;
692 t.gamma_f_used = p.gamma_f;
693 t.neg_damgd_stress = scaled(&p.envlp_neg_stress, 1.0 - t.gamma_f_used);
694 t.low_stress = t.neg_envlp_stress(p, u_min_damgd);
695 t.hgh_strain = c_strain;
696 t.hgh_stress = c_stress;
697 t.gamma_k_used = p.gamma_k;
698 t.k_elastic_pos_damgd = p.k_elastic_pos * (1.0 - t.gamma_k_used);
699 }
700 }
701 _ => {}
702 }
703 }
704
705 if cis {
706 t.state = new_state;
707 }
708}
709
710fn get_state3(
721 strain: &mut [f64; 4],
722 stress: &mut [f64; 4],
723 kunload: f64,
724 t: &Trial,
725 p: &Pinching4Fields,
726) {
727 let kmax = if kunload > t.k_elastic_neg_damgd {
728 kunload
729 } else {
730 t.k_elastic_neg_damgd
731 };
732
733 if strain[0] * strain[3] < 0.0 {
734 strain[1] = t.low_strain * p.r_disp_n;
737 if p.r_force_n - p.u_force_n > 1e-8 {
738 stress[1] = t.low_stress * p.r_force_n;
739 } else if t.min_dmnd < p.envlp_neg_strain[3] {
740 let st1 = t.low_stress * p.u_force_n * (1.0 + 1e-6);
741 let st2 = t.neg_damgd_stress[4] * (1.0 + 1e-6);
742 stress[1] = if st1 < st2 { st1 } else { st2 };
743 } else {
744 let st1 = t.neg_damgd_stress[3] * p.u_force_n * (1.0 + 1e-6);
745 let st2 = t.neg_damgd_stress[4] * (1.0 + 1e-6);
746 stress[1] = if st1 < st2 { st1 } else { st2 };
747 }
748
749 if (stress[1] - stress[0]) / (strain[1] - strain[0]) > t.k_elastic_neg_damgd {
752 strain[1] = t.low_strain + (stress[1] - stress[0]) / t.k_elastic_neg_damgd;
753 }
754
755 if strain[1] > strain[3] {
757 let du = strain[3] - strain[0];
759 let df = stress[3] - stress[0];
760 strain[1] = strain[0] + 0.33 * du;
761 strain[2] = strain[0] + 0.67 * du;
762 stress[1] = stress[0] + 0.33 * df;
763 stress[2] = stress[0] + 0.67 * df;
764 } else {
765 if t.min_dmnd < p.envlp_neg_strain[3] {
766 stress[2] = p.u_force_n * t.neg_damgd_stress[4];
767 } else {
768 stress[2] = p.u_force_n * t.neg_damgd_stress[3];
769 }
770 strain[2] = t.hgh_strain - (t.hgh_stress - stress[2]) / kunload;
771
772 if strain[2] > strain[3] {
773 let du = strain[3] - strain[1];
775 let df = stress[3] - stress[1];
776 strain[2] = strain[1] + 0.5 * du;
777 stress[2] = stress[1] + 0.5 * df;
778 } else if (stress[2] - stress[1]) / (strain[2] - strain[1]) > kmax {
779 let du = strain[3] - strain[0];
781 let df = stress[3] - stress[0];
782 strain[1] = strain[0] + 0.33 * du;
783 strain[2] = strain[0] + 0.67 * du;
784 stress[1] = stress[0] + 0.33 * df;
785 stress[2] = stress[0] + 0.67 * df;
786 } else if (strain[2] < strain[1])
787 || ((stress[2] - stress[1]) / (strain[2] - strain[1]) < 0.0)
788 {
789 if strain[2] < 0.0 {
790 let du = strain[3] - strain[1];
792 let df = stress[3] - stress[1];
793 strain[2] = strain[1] + 0.5 * du;
794 stress[2] = stress[1] + 0.5 * df;
795 } else if strain[1] > 0.0 {
796 let du = strain[2] - strain[0];
798 let df = stress[2] - stress[0];
799 strain[1] = strain[0] + 0.5 * du;
800 stress[1] = stress[0] + 0.5 * df;
801 } else {
802 let avgforce = 0.5 * (stress[2] + stress[1]);
803 let dfr = if avgforce < 0.0 {
804 -avgforce / 100.0
805 } else {
806 avgforce / 100.0
807 };
808 let slope12 = (stress[1] - stress[0]) / (strain[1] - strain[0]);
809 let slope34 = (stress[3] - stress[2]) / (strain[3] - strain[2]);
810 stress[1] = avgforce - dfr;
811 stress[2] = avgforce + dfr;
812 strain[1] = strain[0] + (stress[1] - stress[0]) / slope12;
813 strain[2] = strain[3] - (stress[3] - stress[2]) / slope34;
814 }
815 }
816 }
817 } else {
818 let du = strain[3] - strain[0];
820 let df = stress[3] - stress[0];
821 strain[1] = strain[0] + 0.33 * du;
822 strain[2] = strain[0] + 0.67 * du;
823 stress[1] = stress[0] + 0.33 * df;
824 stress[2] = stress[0] + 0.67 * df;
825 }
826
827 final_check(strain, stress);
828}
829
830#[allow(clippy::if_same_then_else)]
842fn get_state4(
843 strain: &mut [f64; 4],
844 stress: &mut [f64; 4],
845 kunload: f64,
846 t: &Trial,
847 p: &Pinching4Fields,
848) {
849 let kmax = if kunload > t.k_elastic_pos_damgd {
850 kunload
851 } else {
852 t.k_elastic_pos_damgd
853 };
854
855 if strain[0] * strain[3] < 0.0 {
856 strain[2] = t.hgh_strain * p.r_disp_p;
858 if p.u_force_p == 0.0 {
859 stress[2] = t.hgh_stress * p.r_force_p;
860 } else if p.r_force_p - p.u_force_p > 1e-8 {
861 stress[2] = t.hgh_stress * p.r_force_p;
862 } else if t.max_dmnd > p.envlp_pos_strain[3] {
863 let st1 = t.hgh_stress * p.u_force_p * (1.0 + 1e-6);
864 let st2 = t.pos_damgd_stress[4] * (1.0 + 1e-6);
865 stress[2] = if st1 > st2 { st1 } else { st2 };
866 } else {
867 let st1 = t.pos_damgd_stress[3] * p.u_force_p * (1.0 + 1e-6);
868 let st2 = t.pos_damgd_stress[4] * (1.0 + 1e-6);
869 stress[2] = if st1 > st2 { st1 } else { st2 };
870 }
871
872 if (stress[3] - stress[2]) / (strain[3] - strain[2]) > t.k_elastic_pos_damgd {
875 strain[2] = t.hgh_strain - (stress[3] - stress[2]) / t.k_elastic_pos_damgd;
876 }
877
878 if strain[2] < strain[0] {
880 let du = strain[3] - strain[0];
882 let df = stress[3] - stress[0];
883 strain[1] = strain[0] + 0.33 * du;
884 strain[2] = strain[0] + 0.67 * du;
885 stress[1] = stress[0] + 0.33 * df;
886 stress[2] = stress[0] + 0.67 * df;
887 } else {
888 if t.max_dmnd > p.envlp_pos_strain[3] {
889 stress[1] = p.u_force_p * t.pos_damgd_stress[4];
890 } else {
891 stress[1] = p.u_force_p * t.pos_damgd_stress[3];
892 }
893 strain[1] = t.low_strain + (-t.low_stress + stress[1]) / kunload;
894
895 if strain[1] < strain[0] {
896 let du = strain[2] - strain[0];
898 let df = stress[2] - stress[0];
899 strain[1] = strain[0] + 0.5 * du;
900 stress[1] = stress[0] + 0.5 * df;
901 } else if (stress[2] - stress[1]) / (strain[2] - strain[1]) > kmax {
902 let du = strain[3] - strain[0];
904 let df = stress[3] - stress[0];
905 strain[1] = strain[0] + 0.33 * du;
906 strain[2] = strain[0] + 0.67 * du;
907 stress[1] = stress[0] + 0.33 * df;
908 stress[2] = stress[0] + 0.67 * df;
909 } else if (strain[2] < strain[1])
910 || ((stress[2] - stress[1]) / (strain[2] - strain[1]) < 0.0)
911 {
912 if strain[1] > 0.0 {
913 let du = strain[2] - strain[0];
915 let df = stress[2] - stress[0];
916 strain[1] = strain[0] + 0.5 * du;
917 stress[1] = stress[0] + 0.5 * df;
918 } else if strain[2] < 0.0 {
919 let du = strain[3] - strain[1];
921 let df = stress[3] - stress[1];
922 strain[2] = strain[1] + 0.5 * du;
923 stress[2] = stress[1] + 0.5 * df;
924 } else {
925 let avgforce = 0.5 * (stress[2] + stress[1]);
926 let dfr = if avgforce < 0.0 {
927 -avgforce / 100.0
928 } else {
929 avgforce / 100.0
930 };
931 let slope12 = (stress[1] - stress[0]) / (strain[1] - strain[0]);
932 let slope34 = (stress[3] - stress[2]) / (strain[3] - strain[2]);
933 stress[1] = avgforce - dfr;
934 stress[2] = avgforce + dfr;
935 strain[1] = strain[0] + (stress[1] - stress[0]) / slope12;
936 strain[2] = strain[3] - (stress[3] - stress[2]) / slope34;
937 }
938 }
939 }
940 } else {
941 let du = strain[3] - strain[0];
943 let df = stress[3] - stress[0];
944 strain[1] = strain[0] + 0.33 * du;
945 strain[2] = strain[0] + 0.67 * du;
946 stress[1] = stress[0] + 0.33 * df;
947 stress[2] = stress[0] + 0.67 * df;
948 }
949
950 final_check(strain, stress);
951}
952
953fn final_check(strain: &mut [f64; 4], stress: &mut [f64; 4]) {
966 let check_slope = stress[0] / strain[0];
967 let mut slope = 0.0;
968
969 let mut i = 0;
970 while i < 3 {
971 let du = strain[i + 1] - strain[i];
972 let df = stress[i + 1] - stress[i];
973 if du < 0.0 || df < 0.0 {
974 let du = strain[3] - strain[0];
975 let df = stress[3] - stress[0];
976 strain[1] = strain[0] + 0.33 * du;
977 strain[2] = strain[0] + 0.67 * du;
978 stress[1] = stress[0] + 0.33 * df;
979 stress[2] = stress[0] + 0.67 * df;
980 slope = df / du;
981 i = 3;
982 }
983 if slope > 1e-8 && slope < check_slope {
984 strain[1] = 0.0;
985 stress[1] = 0.0;
986 strain[2] = strain[3] / 2.0;
987 stress[2] = stress[3] / 2.0;
988 }
989 i += 1;
990 }
991}
992
993fn envlp34_stress(strain: &[f64; 4], stress: &[f64; 4], u: f64) -> f64 {
998 let mut k = 0.0;
999 let mut f = 0.0;
1000 for i in 0..3 {
1001 if u >= strain[i] {
1002 k = (stress[i + 1] - stress[i]) / (strain[i + 1] - strain[i]);
1003 f = stress[i] + (u - strain[i]) * k;
1004 }
1005 }
1006 if k == 0.0 {
1007 let i = if u < strain[0] { 0 } else { 2 };
1008 k = (stress[i + 1] - stress[i]) / (strain[i + 1] - strain[i]);
1009 f = stress[i] + (u - strain[i]) * k;
1010 }
1011 f
1012}
1013
1014fn envlp34_tangent(strain: &[f64; 4], stress: &[f64; 4], u: f64) -> f64 {
1017 let mut k = 0.0;
1018 for i in 0..3 {
1019 if u >= strain[i] {
1020 k = (stress[i + 1] - stress[i]) / (strain[i + 1] - strain[i]);
1021 }
1022 }
1023 if k == 0.0 {
1024 let i = if u < strain[0] { 0 } else { 2 };
1025 k = (stress[i + 1] - stress[i]) / (strain[i + 1] - strain[i]);
1026 }
1027 k
1028}
1029
1030fn update_dmg(
1043 t: &mut Trial,
1044 p: &Pinching4Fields,
1045 strain: f64,
1046 dstrain: f64,
1047 elastic_strain_energy: f64,
1048 c_n_cycle: f64,
1049) {
1050 let umax_abs = if t.max_dmnd > -t.min_dmnd {
1051 t.max_dmnd
1052 } else {
1053 -t.min_dmnd
1054 };
1055 let uult_abs = if p.envlp_pos_strain[4] > -p.envlp_neg_strain[4] {
1056 p.envlp_pos_strain[4]
1057 } else {
1058 -p.envlp_neg_strain[4]
1059 };
1060 t.n_cycle = c_n_cycle + dstrain.abs() / (4.0 * umax_abs);
1061
1062 let within = strain < uult_abs && strain > -uult_abs;
1063
1064 if within && t.energy < p.energy_capacity {
1065 t.gamma_k = p.gamma_k_params[0] * (umax_abs / uult_abs).powf(p.gamma_k_params[2]);
1066 t.gamma_d = p.gamma_d_params[0] * (umax_abs / uult_abs).powf(p.gamma_d_params[2]);
1067 t.gamma_f = p.gamma_f_params[0] * (umax_abs / uult_abs).powf(p.gamma_f_params[2]);
1068
1069 if t.energy > elastic_strain_energy && p.dmg_cyc == Pinching4DmgCyc::EnergyBased {
1070 let tes = (t.energy - elastic_strain_energy) / p.energy_capacity;
1071 t.gamma_k += p.gamma_k_params[1] * tes.powf(p.gamma_k_params[3]);
1072 t.gamma_d += p.gamma_d_params[1] * tes.powf(p.gamma_d_params[3]);
1073 t.gamma_f += p.gamma_f_params[1] * tes.powf(p.gamma_f_params[3]);
1074 } else if p.dmg_cyc == Pinching4DmgCyc::CycleBased {
1075 t.gamma_k += p.gamma_k_params[1] * t.n_cycle.powf(p.gamma_k_params[3]);
1076 t.gamma_d += p.gamma_d_params[1] * t.n_cycle.powf(p.gamma_d_params[3]);
1077 t.gamma_f += p.gamma_f_params[1] * t.n_cycle.powf(p.gamma_f_params[3]);
1078 }
1079
1080 let gamma_k_lim_env = gamma_k_lim_env(t, p);
1081 let k1 = if t.gamma_k < p.gamma_k_limit {
1082 t.gamma_k
1083 } else {
1084 p.gamma_k_limit
1085 };
1086 t.gamma_k = if k1 < gamma_k_lim_env {
1087 k1
1088 } else {
1089 gamma_k_lim_env
1090 };
1091 if t.gamma_d >= p.gamma_d_limit {
1092 t.gamma_d = p.gamma_d_limit;
1093 }
1094 if t.gamma_f >= p.gamma_f_limit {
1095 t.gamma_f = p.gamma_f_limit;
1096 }
1097 } else if within {
1098 let gamma_k_lim_env = gamma_k_lim_env(t, p);
1099 t.gamma_k = if p.gamma_k_limit < gamma_k_lim_env {
1100 p.gamma_k_limit
1101 } else {
1102 gamma_k_lim_env
1103 };
1104 t.gamma_d = p.gamma_d_limit;
1105 t.gamma_f = p.gamma_f_limit;
1106 }
1107}
1108
1109fn gamma_k_lim_env(t: &Trial, p: &Pinching4Fields) -> f64 {
1117 let kmin_p = t.pos_envlp_stress(p, t.max_dmnd) / t.max_dmnd;
1118 let kmin_n = t.neg_envlp_stress(p, t.min_dmnd) / t.min_dmnd;
1119 let kmin = if (kmin_p / p.k_elastic_pos) > (kmin_n / p.k_elastic_neg) {
1120 kmin_p / p.k_elastic_pos
1121 } else {
1122 kmin_n / p.k_elastic_neg
1123 };
1124 if 0.0 > (1.0 - kmin) {
1125 0.0
1126 } else {
1127 1.0 - kmin
1128 }
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133 use super::*;
1134
1135 fn pinching4_test_material(r_disp: f64, r_force: f64, u_force: f64) -> Material {
1148 Material::pinching4(
1149 10.0,
1150 0.01,
1151 15.0,
1152 0.02,
1153 17.0,
1154 0.03,
1155 10.0,
1156 0.04,
1157 -10.0,
1158 -0.01,
1159 -15.0,
1160 -0.02,
1161 -17.0,
1162 -0.03,
1163 -10.0,
1164 -0.04,
1165 r_disp,
1166 r_force,
1167 u_force,
1168 r_disp,
1169 r_force,
1170 u_force,
1171 [0.0; 4],
1172 0.0,
1173 [0.0; 4],
1174 0.0,
1175 [0.0; 4],
1176 0.0,
1177 10.0,
1178 Pinching4DmgCyc::EnergyBased,
1179 )
1180 }
1181
1182 #[test]
1195 fn pinching4_gives_exact_initial_tangent_at_zero_strain() {
1196 let m = pinching4_test_material(0.2, 0.4, 0.05);
1197 let (stress, tangent) = m.trial_stress_tangent(0.0);
1198 assert_eq!(stress, 0.0);
1199 assert!((tangent - 1000.0).abs() < 1e-9, "got {tangent}");
1200 assert_eq!(m.initial_tangent(), tangent);
1201 }
1202
1203 #[test]
1212 fn pinching4_matches_the_six_point_envelope_on_first_loading() {
1213 let m = pinching4_test_material(0.2, 0.4, 0.05).commit(0.005);
1218
1219 let close = |got: (f64, f64), want: (f64, f64)| {
1220 assert!(
1221 (got.0 - want.0).abs() < 1e-9,
1222 "stress: got {}, want {}",
1223 got.0,
1224 want.0
1225 );
1226 assert!(
1227 (got.1 - want.1).abs() < 1e-9,
1228 "tangent: got {}, want {}",
1229 got.1,
1230 want.1
1231 );
1232 };
1233
1234 close(m.trial_stress_tangent(0.005), (5.0, 1000.0));
1235 close(m.trial_stress_tangent(0.01), (10.0, 1000.0)); close(m.trial_stress_tangent(0.015), (12.5, 500.0));
1237 close(m.trial_stress_tangent(0.02), (15.0, 500.0)); close(m.trial_stress_tangent(0.025), (16.0, 200.0));
1239 close(m.trial_stress_tangent(0.03), (17.0, 200.0)); close(m.trial_stress_tangent(0.035), (13.5, -700.0)); close(m.trial_stress_tangent(0.04), (10.0, -700.0)); let k5 = (11.0 - 10.0) / (40000.0 - 0.04);
1248 close(m.trial_stress_tangent(0.05), (10.0 + 0.01 * k5, k5));
1249 }
1250
1251 #[test]
1271 fn pinching4_reloads_along_a_clean_trilinear_path_after_a_reversal() {
1272 let m = pinching4_test_material(0.2, 0.4, 0.05)
1273 .commit(0.005)
1274 .commit(0.02);
1275
1276 let (stress, tangent) = m.trial_stress_tangent(-0.005);
1279 assert!((stress - -6.25).abs() < 1e-9, "got {stress}");
1280 assert!((tangent - 750.0).abs() < 1e-9, "got {tangent}");
1281
1282 let k = 3.15 / 0.00615;
1286 let (stress, tangent) = m.trial_stress_tangent(0.0);
1287 assert!((stress - (-4.0 + 0.002 * k)).abs() < 1e-9, "got {stress}");
1288 assert!((tangent - k).abs() < 1e-9, "got {tangent}");
1289 assert!(
1290 tangent < 750.0,
1291 "the pinched segment should be softer than the reload segment, got {tangent}"
1292 );
1293 }
1294
1295 #[test]
1309 fn pinching4_falls_back_to_a_linear_path_when_the_reload_segment_is_too_stiff() {
1310 let m = pinching4_test_material(0.05, 0.9, 0.05)
1311 .commit(0.005)
1312 .commit(0.02);
1313
1314 let k = 8.25 / 0.0099;
1317 let (stress, tangent) = m.trial_stress_tangent(-0.005);
1318 assert!((stress - (-10.0 + 0.005 * k)).abs() < 1e-9, "got {stress}");
1319 assert!((tangent - k).abs() < 1e-9, "got {tangent}");
1320 assert!((stress - -5.833333333333333).abs() < 1e-9, "got {stress}");
1321
1322 assert!(
1327 (tangent - 1.0 / 0.0095).abs() > 1.0,
1328 "expected the fallback path, not the raw trilinear construction"
1329 );
1330 }
1331
1332 #[test]
1338 fn pinching4_reverses_from_state_three_into_state_four() {
1339 let m = pinching4_test_material(0.2, 0.4, 0.05)
1340 .commit(0.005)
1341 .commit(0.02)
1342 .commit(-0.005)
1343 .commit(0.0);
1344 let Material::Pinching4(f) = &m else { panic!() };
1345 assert_eq!(f.state, Pinching4State::Trilinear4);
1346 assert!((f.low_state_strain - -0.005).abs() < 1e-12);
1350 assert!((f.hgh_state_strain - 0.02).abs() < 1e-12);
1351 assert!(
1354 (f.low_state_stress - -6.25).abs() < 1e-9,
1355 "got {}",
1356 f.low_state_stress
1357 );
1358 assert!(
1362 (f.hgh_state_stress - 15.0).abs() < 1e-9,
1363 "got {}",
1364 f.hgh_state_stress
1365 );
1366 let (stress, _) = m.trial_stress_tangent(0.02);
1367 assert!((stress - 15.0).abs() < 1e-9, "got {stress}");
1368 }
1369}