oxiphysics_materials/fracture/types.rs
1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#[allow(unused_imports)]
6use super::functions::*;
7#[allow(unused_imports)]
8use super::functions_2::*;
9use std::f64::consts::PI;
10
11/// CTOD (Crack Tip Opening Displacement) fracture criterion.
12///
13/// Failure is predicted when CTOD reaches a material-dependent critical value.
14/// The plastic zone size at the crack tip is computed using the Irwin or Dugdale
15/// model.
16#[allow(dead_code)]
17#[derive(Debug, Clone, Copy)]
18pub struct CtodCriterion {
19 /// Critical CTOD δ_c (m).
20 pub ctod_critical: f64,
21 /// Young's modulus E (Pa).
22 pub young_modulus: f64,
23 /// Poisson's ratio ν.
24 pub poisson_ratio: f64,
25 /// Yield stress σ_ys (Pa).
26 pub yield_stress: f64,
27}
28#[allow(dead_code)]
29impl CtodCriterion {
30 /// Create a new CTOD criterion model.
31 pub fn new(
32 ctod_critical: f64,
33 young_modulus: f64,
34 poisson_ratio: f64,
35 yield_stress: f64,
36 ) -> Self {
37 Self {
38 ctod_critical,
39 young_modulus,
40 poisson_ratio,
41 yield_stress,
42 }
43 }
44 /// Structural steel default (δ_c = 0.1 mm, E = 200 GPa, σ_ys = 355 MPa).
45 pub fn structural_steel() -> Self {
46 Self::new(0.1e-3, 200.0e9, 0.3, 355.0e6)
47 }
48 /// Compute the Irwin plastic zone radius.
49 ///
50 /// Plane stress: r_p = (1/(2π)) * (K_I / σ_ys)²
51 /// Plane strain: r_p = (1/(6π)) * (K_I / σ_ys)²
52 pub fn compute_plastic_zone_size(&self, k_i: f64, plane_strain: bool) -> f64 {
53 let factor = if plane_strain {
54 1.0 / (6.0 * PI)
55 } else {
56 1.0 / (2.0 * PI)
57 };
58 factor * (k_i / self.yield_stress).powi(2)
59 }
60 /// Compute the Dugdale (strip-yield) plastic zone size.
61 ///
62 /// c = a * (sec(π σ / (2 σ_ys)) - 1)
63 pub fn compute_dugdale_plastic_zone(&self, half_crack: f64, applied_stress: f64) -> f64 {
64 let arg = PI * applied_stress / (2.0 * self.yield_stress);
65 if arg.abs() >= PI / 2.0 - 1e-6 {
66 return f64::INFINITY;
67 }
68 half_crack * (1.0 / arg.cos() - 1.0)
69 }
70 /// Check whether the CTOD criterion is met (fracture initiates).
71 ///
72 /// Returns `true` if δ >= δ_c.
73 pub fn is_critical(&self, ctod: f64) -> bool {
74 ctod >= self.ctod_critical
75 }
76 /// Compute CTOD from stress intensity factor.
77 ///
78 /// Plane stress: δ = K_I² / (E σ_ys)
79 /// Plane strain: δ = K_I² (1-ν²) / (E σ_ys)
80 pub fn ctod_from_sif(&self, k_i: f64, plane_strain: bool) -> f64 {
81 let e_prime = if plane_strain {
82 self.young_modulus / (1.0 - self.poisson_ratio * self.poisson_ratio)
83 } else {
84 self.young_modulus
85 };
86 k_i * k_i / (e_prime * self.yield_stress)
87 }
88 /// Compute the critical SIF K_Ic from the critical CTOD.
89 ///
90 /// K_Ic = sqrt(δ_c * E' * σ_ys)
91 pub fn critical_sif(&self, plane_strain: bool) -> f64 {
92 let e_prime = if plane_strain {
93 self.young_modulus / (1.0 - self.poisson_ratio * self.poisson_ratio)
94 } else {
95 self.young_modulus
96 };
97 (self.ctod_critical * e_prime * self.yield_stress).sqrt()
98 }
99}
100/// Parameters for the AT2 phase field fracture model.
101#[allow(dead_code)]
102#[derive(Debug, Clone)]
103pub struct PhaseFieldParams {
104 /// Critical energy release rate (J/m²)
105 pub gc: f64,
106 /// Internal length scale (m)
107 pub l0: f64,
108 /// Young's modulus (Pa)
109 pub e: f64,
110 /// Poisson's ratio (dimensionless)
111 pub nu: f64,
112}
113impl PhaseFieldParams {
114 /// Create new phase field parameters.
115 pub fn new(gc: f64, l0: f64, e: f64, nu: f64) -> Self {
116 Self { gc, l0, e, nu }
117 }
118}
119/// Parameters for the Gurson-Tvergaard-Needleman ductile fracture model.
120#[allow(dead_code)]
121#[derive(Debug, Clone)]
122pub struct GtnParams {
123 /// Tvergaard correction factor q1 (≈1.5)
124 pub q1: f64,
125 /// Tvergaard correction factor q2 (≈1.0)
126 pub q2: f64,
127 /// Tvergaard correction factor q3 = q1² (≈2.25)
128 pub q3: f64,
129 /// Initial void volume fraction
130 pub f0: f64,
131 /// Critical void fraction at the onset of coalescence
132 pub fc: f64,
133 /// Void fraction at complete fracture
134 pub ff: f64,
135 /// Nucleation void volume fraction amplitude
136 pub fn_void: f64,
137 /// Standard deviation of the nucleation strain distribution
138 pub sn: f64,
139 /// Mean nucleation strain
140 pub en: f64,
141 /// Uniaxial yield stress (Pa)
142 pub sigma_y: f64,
143 /// Young's modulus (Pa)
144 pub e: f64,
145 /// Poisson's ratio (dimensionless)
146 pub nu: f64,
147}
148impl GtnParams {
149 /// Typical GTN parameters for structural steel.
150 pub fn steel() -> Self {
151 Self {
152 q1: 1.5,
153 q2: 1.0,
154 q3: 2.25,
155 f0: 0.001,
156 fc: 0.15,
157 ff: 0.25,
158 fn_void: 0.04,
159 sn: 0.1,
160 en: 0.3,
161 sigma_y: 250.0e6,
162 e: 200.0e9,
163 nu: 0.3,
164 }
165 }
166}
167/// Griffith fracture criterion for brittle materials.
168///
169/// Fracture occurs when the strain energy release rate G equals or exceeds
170/// the critical energy release rate G_c.
171///
172/// For mode I: G = K_I² / E'
173/// For mode II: G = K_II² / E'
174/// For mode III: G = K_III² / (2 μ)
175///
176/// where E' = E for plane stress and E' = E / (1-ν²) for plane strain.
177#[allow(dead_code)]
178#[derive(Debug, Clone, Copy)]
179pub struct GriffithCriterion {
180 /// Critical energy release rate G_c (J/m²).
181 pub g_critical: f64,
182 /// Young's modulus E (Pa).
183 pub young_modulus: f64,
184 /// Poisson's ratio ν.
185 pub poisson_ratio: f64,
186 /// Shear modulus G (Pa) = E / (2(1+ν)).
187 pub shear_modulus: f64,
188}
189#[allow(dead_code)]
190impl GriffithCriterion {
191 /// Create a Griffith criterion model.
192 pub fn new(g_critical: f64, young_modulus: f64, poisson_ratio: f64) -> Self {
193 let shear_modulus = young_modulus / (2.0 * (1.0 + poisson_ratio));
194 Self {
195 g_critical,
196 young_modulus,
197 poisson_ratio,
198 shear_modulus,
199 }
200 }
201 /// Steel default (G_c = 100 J/m², E = 200 GPa, ν = 0.3).
202 pub fn steel() -> Self {
203 Self::new(100.0, 200.0e9, 0.3)
204 }
205 /// Effective modulus E' for plane stress or plane strain.
206 pub fn effective_modulus(&self, plane_strain: bool) -> f64 {
207 if plane_strain {
208 self.young_modulus / (1.0 - self.poisson_ratio * self.poisson_ratio)
209 } else {
210 self.young_modulus
211 }
212 }
213 /// Compute the energy release rate for mode I loading.
214 ///
215 /// G_I = K_I² / E'
216 pub fn compute_energy_release_rate_mode_i(&self, k_i: f64, plane_strain: bool) -> f64 {
217 let e_prime = self.effective_modulus(plane_strain);
218 k_i * k_i / e_prime
219 }
220 /// Compute the energy release rate for mixed mode (I + II + III).
221 ///
222 /// G = K_I²/E' + K_II²/E' + K_III²/(2μ)
223 pub fn compute_energy_release_rate(
224 &self,
225 k_i: f64,
226 k_ii: f64,
227 k_iii: f64,
228 plane_strain: bool,
229 ) -> f64 {
230 let e_prime = self.effective_modulus(plane_strain);
231 let g_mode1 = k_i * k_i / e_prime;
232 let g_mode2 = k_ii * k_ii / e_prime;
233 let g_mode3 = k_iii * k_iii / (2.0 * self.shear_modulus);
234 g_mode1 + g_mode2 + g_mode3
235 }
236 /// Check whether the Griffith criterion is satisfied (fracture occurs).
237 ///
238 /// Returns `true` if G >= G_c.
239 pub fn is_critical(&self, k_i: f64, k_ii: f64, k_iii: f64, plane_strain: bool) -> bool {
240 self.compute_energy_release_rate(k_i, k_ii, k_iii, plane_strain) >= self.g_critical
241 }
242 /// Critical SIF K_Ic from G_c (mode I, plane strain).
243 ///
244 /// K_Ic = sqrt(G_c * E')
245 pub fn critical_sif(&self, plane_strain: bool) -> f64 {
246 let e_prime = self.effective_modulus(plane_strain);
247 (self.g_critical * e_prime).sqrt()
248 }
249 /// Compute the critical half-crack length a_c for a given applied stress.
250 ///
251 /// a_c = G_c * E' / (π * σ²)
252 pub fn critical_crack_length(&self, stress: f64, plane_strain: bool) -> f64 {
253 let e_prime = self.effective_modulus(plane_strain);
254 if stress.abs() < f64::EPSILON {
255 return f64::INFINITY;
256 }
257 self.g_critical * e_prime / (PI * stress * stress)
258 }
259}
260/// R-curve model: fracture resistance as a function of crack extension.
261///
262/// Models the rising resistance to crack growth due to crack-wake processes
263/// (bridging, plastic zone development, etc.).
264#[allow(dead_code)]
265#[derive(Debug, Clone)]
266pub struct RCurve {
267 /// Initial fracture toughness K_Ic or G_Ic (at initiation).
268 pub k_init: f64,
269 /// Plateau (steady-state) fracture toughness.
270 pub k_plateau: f64,
271 /// Characteristic length for the transition (m).
272 pub delta_a_char: f64,
273}
274#[allow(dead_code)]
275impl RCurve {
276 /// Create a new R-curve model.
277 pub fn new(k_init: f64, k_plateau: f64, delta_a_char: f64) -> Self {
278 Self {
279 k_init,
280 k_plateau,
281 delta_a_char,
282 }
283 }
284 /// Evaluate the R-curve at a given crack extension Δa.
285 ///
286 /// K_R(Δa) = K_init + (K_plateau - K_init) * (1 - exp(-Δa / Δa_char))
287 pub fn resistance(&self, delta_a: f64) -> f64 {
288 if delta_a <= 0.0 {
289 return self.k_init;
290 }
291 self.k_init + (self.k_plateau - self.k_init) * (1.0 - (-delta_a / self.delta_a_char).exp())
292 }
293 /// Check crack stability: crack grows stably if K_applied ≤ K_R(Δa).
294 ///
295 /// Returns `true` if the crack is stable (no unstable fracture).
296 pub fn is_stable(&self, k_applied: f64, delta_a: f64) -> bool {
297 k_applied <= self.resistance(delta_a)
298 }
299 /// Find the critical crack extension at which instability occurs.
300 ///
301 /// For a linearly increasing applied K: K = K0 + dK/da * Δa,
302 /// find Δa where the driving force tangent equals the R-curve tangent.
303 ///
304 /// Returns the critical crack extension, or None if always stable.
305 pub fn critical_extension(&self, k0: f64, dk_da: f64) -> Option<f64> {
306 let delta_k = self.k_plateau - self.k_init;
307 if delta_k <= 0.0 || dk_da <= 0.0 {
308 if k0 >= self.k_init {
309 return Some(0.0);
310 }
311 return None;
312 }
313 let slope_init = delta_k / self.delta_a_char;
314 if dk_da >= slope_init {
315 return Some(0.0);
316 }
317 let delta_a_crit = -self.delta_a_char * (dk_da / slope_init).ln();
318 let k_r = self.resistance(delta_a_crit);
319 let k_app = k0 + dk_da * delta_a_crit;
320 if k_app >= k_r {
321 Some(delta_a_crit)
322 } else {
323 None
324 }
325 }
326}
327impl RCurve {
328 /// Simulate stable crack growth under a monotonically increasing applied K.
329 ///
330 /// Starting from `a0` with applied K = `k0 + dk_da * Δa`, integrate the
331 /// stable crack extension Δa until either:
332 /// - instability (dK_R/dΔa < dK_applied/dΔa), or
333 /// - the maximum crack extension `delta_a_max` is reached.
334 ///
335 /// Returns a vector of (Δa, K_applied, K_R) tuples.
336 pub fn compute_stable_crack_growth(
337 &self,
338 k0: f64,
339 dk_da: f64,
340 delta_a_max: f64,
341 n_steps: usize,
342 ) -> Vec<(f64, f64, f64)> {
343 if n_steps == 0 || delta_a_max <= 0.0 {
344 return Vec::new();
345 }
346 let step = delta_a_max / n_steps as f64;
347 let mut result = Vec::with_capacity(n_steps + 1);
348 for i in 0..=n_steps {
349 let da = i as f64 * step;
350 let k_app = k0 + dk_da * da;
351 let k_r = self.resistance(da);
352 result.push((da, k_app, k_r));
353 if k_app > k_r + 1e-6 * k_r {
354 break;
355 }
356 }
357 result
358 }
359 /// Minimum applied K0 needed to initiate stable crack growth.
360 ///
361 /// Crack initiates when K_applied >= K_init (= K_R at Δa = 0).
362 pub fn initiation_load(&self) -> f64 {
363 self.k_init
364 }
365 /// Fracture instability load: K at which dK/da = dK_R/dΔa.
366 ///
367 /// For a linear loading: K = K0 + (dK/da) * Δa with slope `dk_da`,
368 /// returns the K level at the tangency point, or None if always stable.
369 pub fn instability_load(&self, dk_da: f64) -> Option<f64> {
370 let delta_a = self.critical_extension(self.k_init, dk_da)?;
371 Some(self.k_init + dk_da * delta_a)
372 }
373}