Skip to main content

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}