Source code for engineeringkit.materials.systems.fracture

r"""Fracture mechanics: Griffith's energy balance, Irwin's K and Paris's law.

Griffith showed that a crack grows when the elastic energy it releases pays
for the new surface, which makes strength fall as :math:`1/\sqrt{a}`. Irwin
recast the balance as the stress intensity factor
:math:`K = Y\sigma\sqrt{\pi a}` reaching the toughness :math:`K_{Ic}`. Paris
found that a fatigue crack grows by a power of the range of :math:`K` each
cycle.
"""

from __future__ import annotations

from collections.abc import Callable

import numpy as np
from scipy.integrate import cumulative_trapezoid

from engineeringkit._validation import integer as _integer
from engineeringkit._validation import nonnegative as _nonnegative
from engineeringkit._validation import positive as _positive
from engineeringkit.materials.core.base import CrackGrowthResult

__all__ = ["griffith_fracture_stress", "stress_intensity", "critical_crack_length", "paris_crack_growth", "crack_growth_life"]


[docs] def griffith_fracture_stress(E, gamma, a): r"""Griffith's fracture stress of a brittle plate with a central crack. .. math:: \sigma_f = \sqrt{\frac{2E\gamma_s}{\pi a}} Parameters ---------- E : float or array_like Young's modulus [Pa], positive. gamma : float or array_like Surface energy :math:`\gamma_s` [J/m^2], positive. a : float or array_like Half-length of the crack [m], positive. Returns ------- float or ndarray Remote tensile stress at fracture [Pa], plane stress. Notes ----- Ideally brittle solid; plastic work at the tip, which dominates in metals, is left out. Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/materials`. References ---------- A. A. Griffith, "The phenomena of rupture and flow in solids," *Phil. Trans. R. Soc. Lond. A* 221 (1921) 163-198. """ _positive(E=E, gamma=gamma, a=a) return np.sqrt(2 * E * gamma / (np.pi * np.asarray(a)))
[docs] def stress_intensity(K_geometry, sigma, a): r"""Mode-I stress intensity factor :math:`K_I = Y\sigma\sqrt{\pi a}`. Parameters ---------- K_geometry : float or array_like Geometry factor :math:`Y` [-], positive (1 for a central crack in an infinite plate, about 1.12 for an edge crack). sigma : float or array_like Remote tensile stress [Pa], nonnegative. a : float or array_like Crack length (edge) or half-length (central) [m], positive. Returns ------- float or ndarray Stress intensity [Pa sqrt(m)]. Notes ----- Linear-elastic fracture mechanics, valid while the plastic zone is small compared with the crack. Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/materials`. References ---------- G. R. Irwin, "Analysis of stresses and strains near the end of a crack traversing a plate," *J. Appl. Mech.* 24 (1957) 361-364. """ _positive(K_geometry=K_geometry, a=a) _nonnegative(sigma=sigma) return K_geometry * np.asarray(sigma) * np.sqrt(np.pi * np.asarray(a))
[docs] def critical_crack_length(Kic, sigma, Y=1.0): r"""Crack length at which :math:`K_I` reaches the toughness, :math:`a_c = (K_{Ic}/Y\sigma)^2/\pi`. Parameters ---------- Kic : float or array_like Plane-strain fracture toughness [Pa sqrt(m)], positive. sigma : float or array_like Remote tensile stress [Pa], positive. Y : float or array_like Geometry factor [-], positive. Returns ------- float or ndarray Critical crack length [m]. Notes ----- Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/materials`. """ _positive(Kic=Kic, sigma=sigma, Y=Y) return (Kic / (Y * np.asarray(sigma))) ** 2 / np.pi
[docs] def paris_crack_growth(C, m, delta_K): r"""Fatigue crack growth rate from Paris's law, :math:`da/dN = C\,\Delta K^m`. Parameters ---------- C : float or array_like Paris coefficient [m/cycle per (Pa sqrt(m))^m], nonnegative. Values quoted for MPa sqrt(m) must be multiplied by :math:`10^{-6m}`. m : float or array_like Paris exponent [-], positive (about 3 for steels). delta_K : float or array_like Stress intensity range [Pa sqrt(m)], nonnegative. Returns ------- float or ndarray Growth per cycle [m/cycle]. Notes ----- The stable (Region II) regime only: neither the threshold :math:`\Delta K_{th}` nor the acceleration near :math:`K_{Ic}` is modelled. Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/materials`. References ---------- P. Paris and F. Erdogan, "A critical analysis of crack propagation laws," *J. Basic Eng.* 85 (1963) 528-533. """ _nonnegative(C=C, delta_K=delta_K) _positive(m=m) return C * np.asarray(delta_K) ** m
[docs] def crack_growth_life(C, m, delta_sigma, a0, af, Y: float | Callable = 1.0, points=2001): r"""Integrate Paris's law from an initial to a final crack length. .. math:: N = \int_{a_0}^{a_f} \frac{da}{C\,(Y(a)\,\Delta\sigma\sqrt{\pi a})^m} Parameters ---------- C : float Paris coefficient [m/cycle per (Pa sqrt(m))^m], positive. m : float Paris exponent [-], positive. delta_sigma : float Stress range [Pa], positive. a0 : float Initial crack length [m], positive. af : float Final crack length [m], greater than ``a0``; often :func:`critical_crack_length` at the peak stress. Y : float or callable Geometry factor [-], a positive constant or a function ``Y(a)`` of crack length [m] (for example a finite-width correction). points : int Number of geometrically spaced crack lengths, at least 2. Returns ------- CrackGrowthResult Crack length against cycles. Notes ----- All parameters are scalar-only: the result is one integrated crack-growth history. The trapezoid rule on a geometric grid; with a constant ``Y`` and :math:`m \ne 2` the closed form :math:`N = 2(a_0^{1-m/2} - a_f^{1-m/2}) / ((m - 2) C (Y\Delta\sigma\sqrt\pi)^m)` checks it. Inputs are raw SI numbers with no dimensional metadata; see :doc:`/guides/materials`. """ _positive(C=C, m=m, delta_sigma=delta_sigma, a0=a0, af=af) _integer("points", points, 2) if af <= a0: raise ValueError("af must exceed a0") a = np.geomspace(a0, af, points) geometry = np.asarray(Y(a), float) * np.ones_like(a) if callable(Y) else np.full_like(a, Y) _positive(Y=geometry) rate = paris_crack_growth(C, m, stress_intensity(geometry, delta_sigma, a)) cycles = cumulative_trapezoid(1 / rate, a, initial=0) return CrackGrowthResult(crack_length=a, cycles=cycles)