# EPM (Elasto-Plastic Models) Comprehensive Validation Report **Date:** 2026-03-29 **Mode:** Enterprise (--deep --security --performance) **Scope:** `rheojax/models/epm/`, `docs/source/models/epm/`, `examples/epm/`, `tests/models/epm/`, `tests/utils/test_epm_kernels*.py` --- ## Summary - **Assessment:** ⚠️ Needs Minor Work (3 documentation corrections needed) - **Confidence:** High - **Mode:** Enterprise with --deep --security --performance --- ## 1. Automated Checks | Dimension | Tool | Result | |-----------|------|--------| | Unit Tests (model) | pytest | 35/35 PASSED | | Unit Tests (kernels) | pytest | 26/26 PASSED | | Linting | ruff | All checks passed | | Type Checking | mypy | No EPM-specific errors | | Security (S rules) | ruff --select S | 18 `S101` (assert usage) — low risk | --- ## 2. Physics Verification Against Literature ### 2.1 Eshelby Propagator — CORRECT **Implementation:** `G(q) = -2μ·(qx·qy)²/|q|⁴` (Fourier space, `epm_kernels.py:51-53`) **Literature:** Matches Picard et al. (2004) Eur. Phys. J. E 15, 371 and Talamali et al. (2011) Phys. Rev. E 84, 016115. The quadrupolar `cos(4θ)/r²` real-space form is recovered asymptotically. Lattice finite-size corrections are inherently handled by the FFT-based computation. **Numerical stability:** Proper masking at q=0 (`valid_mask = Q2 > 0`, `safe_Q2 = jnp.where(...)`) prevents division by zero. ✓ ### 2.2 Von Mises Yield Criterion — CORRECT **Implementation:** Full 3D formula `σ_eq = √[(σ_xx-σ_yy)² + (σ_yy-σ_zz)² + (σ_zz-σ_xx)² + 6σ_xy²] / √2` with plane-strain constraint `σ_zz = ν(σ_xx + σ_yy)`. **Literature:** Standard `σ_eq = √(3J₂)` formulation. Factor of 6 on shear term is correct for full 3D → plane strain reduction. ✓ ### 2.3 Hill Anisotropic Criterion — CORRECT **Implementation:** `σ_eff² = H·[(σ_xx-σ_yy)² + (σ_yy-σ_zz)² + (σ_zz-σ_xx)²] + 2N·σ_xy²` **Literature:** Matches Hill (1948) "A theory of the yielding and plastic flow of anisotropic metals." Correctly reduces to von Mises when H=1/3, N=1.5. ✓ ### 2.4 Prandtl-Reuss Plastic Flow Rule — CORRECT **Implementation:** `ε̇ᵖ = Γ·σ·activation(|σ| - σ_c)` where Γ = 1/τ_pl. **Literature:** Standard associated flow rule with Bingham-like threshold. Direction aligned with deviatoric stress (normality rule). ✓ ### 2.5 FFT-Based Stress Redistribution — CORRECT **Implementation:** `σ̇(q) = Ĝ(q)·ε̇ᵖ(q)` via `rfft2/irfft2`. **Literature:** O(L²log L) spectral method, standard in all modern EPM implementations (Nicolas et al. 2018 Rev. Mod. Phys.). Conservation law (zero-sum redistribution) verified by tests. ✓ ### 2.6 Hébraud-Lequeux as Mean-Field Limit — CORRECT The lattice EPM is correctly described as a spatial generalization of HL (1998). Replacing the Eshelby kernel with uniform Gaussian noise recovers the HL Fokker-Planck equation. ✓ ### 2.7 Smooth Yielding Approximation — CORRECT (with caveat) **Implementation:** `activation = 0.5·(1 + tanh((|σ| - σ_c)/w))` for JAX differentiability. **Literature:** NOT standard in EPM physics literature (which uses sharp Heaviside). However, this is a legitimate engineering approximation borrowed from structural optimization and ML-elastoplasticity (see ScienceDirect 2025 references). It recovers the sharp limit as w→0. **Caveat: should be documented as a numerical convenience, not a physics feature.** Currently documented in docstrings as "smooth mode for Bayesian inference" — acceptable. --- ## 3. Documentation vs. Code Consistency ### 3.1 Issues Found #### ISSUE 1 (Important): Avalanche Exponent Range Overstated - **Doc claim** (`lattice_epm.rst:1508`): `τ ≈ 1.5-2.0` with "disorder corrections" - **Literature:** Mean-field τ = 3/2 is for *random triggering* only. Under quasistatic loading (the physical protocol), Lin et al. PNAS 2014 measured τ = 1.36±0.03 (2D). The range "1.5-2.0" overstates; should be "1.0-1.5 (2D lattice) to 3/2 (mean-field random triggering)." - **Doc also claims** (`lattice_epm.rst:398`): "power-law exponents closer to τ ≈ 2.0 (with disorder)" — no literature support for τ ≈ 2.0 in standard EPM. #### ISSUE 2 (Important): Creep Fluidization Exponent Weakly Supported - **Doc claim** (`lattice_epm.rst:611`): `t_f ~ (Σ_y - Σ_0)^(-ν)` with `ν ≈ 4-6` from "depinning universality" - **Literature:** Ferrero et al. PRL 129, 208001 (2022) derives β = (1+θ)/(1-n) ≈ 2-3 for athermal EPM. The ν ≈ 4-6 range comes from *thermal* creep experiments (Divoux et al. on carbopol gels), not from athermal depinning universality. The attribution to "depinning universality" is misleading. #### ISSUE 3 (Minor): Parameter Bounds Inconsistency - **Doc** (`lattice_epm.rst`): mu bounds listed as [0.1-100.0 Pa] - **Code** (`base.py:511`): mu bounds are `(0.1, 1e9)` — nine orders of magnitude wider - The doc range is too narrow; code is correct for general-purpose fitting. ### 3.2 No Issues - All 5 protocols (flow, startup, relaxation, creep, oscillation) documented and implemented consistently - Parameter names match between code, docs, and examples - TensorialEPM Hill/von Mises selection documented and implemented correctly - Bayesian pipeline (NLSQ→NUTS) documented and tested end-to-end - Normal stress N₁ predictions correctly described as requiring tensor tracking --- ## 4. Examples vs. Code Consistency ### 4.1 Issues Found #### ISSUE 4 (Minor): TensorialEPM Numerical Instability at L=16 - `01_epm_flow_curve.ipynb` shows TensorialEPM at L=16 producing σ_xy ~170,000 Pa vs data ~295 Pa (5 orders of magnitude off), with N₁/σ_xy ~ 10⁻⁷ (unphysical) - Acknowledged in notebook as Eshelby propagator artifact requiring L≥32 - **Recommendation:** Either increase L to 32 in the example or add a prominent warning cell ### 4.2 No Issues - All 6 example notebooks run the correct protocols with appropriate parameters - Synthetic data generation uses physically reasonable parameter ranges - Real data (mucus, polystyrene) used appropriately - Claims about stress overshoot, viscosity bifurcation, and disorder-induced relaxation spectra are physically correct --- ## 5. Test Coverage Assessment | Area | Tests | Coverage | |------|-------|----------| | Base class init/compat | 8 | Good | | LatticeEPM protocols | 7 | All 5 protocols + params | | TensorialEPM protocols | 18 | All protocols + criteria + bounds + seeds | | Scalar kernels | 5 | Propagator + yielding + step | | Tensorial kernels | 21 | Propagator + von Mises + Hill + flow rule + conservation | | Integration pipeline | 1+ | End-to-end NLSQ workflow | | Bayesian (NLSQ→NUTS) | 1+ | Validation suite | **Edge cases covered:** Zero shear, yield threshold, scalar limit, seed reproducibility, bounds enforcement, Hill↔von Mises reduction, smooth↔hard modes, stress conservation law, plastic strain incompressibility. **Missing tests (suggestions):** - Large L convergence (L=128 vs L=64 flow curves should converge) - Avalanche statistics extraction and exponent fitting - Creep bifurcation at σ ≈ σ_y (critical slowing down) --- ## 6. Security Analysis | Check | Result | |-------|--------| | Secrets in code | None found | | Input sanitization | N/A (numerical library) | | `assert` usage | 18 instances in `base.py`/`tensor.py` — parameter guards, not security-critical | | Dependency vulnerabilities | Not applicable (JAX/NumPy/SciPy stack) | | Code injection | No string eval/exec, no user-supplied code paths | **Assessment:** No security concerns. The 18 `assert` statements are parameter validation guards. In production deployment, these could be replaced with explicit `ValueError` raises, but for a scientific computing library this is standard practice. --- ## 7. Performance Notes - **JIT compilation:** All kernels decorated with `@jax.jit` or called within JIT-compiled scan loops ✓ - **GPU vectorization:** `jax.vmap` used for parallel shear rate/frequency sweeps ✓ - **FFT convolution:** O(L²log L) stress redistribution (vs O(L⁴) direct) ✓ - **Cached propagators:** Eshelby kernel precomputed once per lattice size ✓ - **Smooth mode overhead:** tanh activation adds ~10% overhead vs hard Heaviside (acceptable for AD) --- ## Recommendations ### Must Fix (Documentation) 1. **Correct avalanche exponent range** in `lattice_epm.rst:398,1508`: Change "τ ≈ 1.5-2.0" to "τ ≈ 1.0-1.5 (2D lattice, quasistatic)" with mean-field limit noted separately 2. **Correct creep fluidization attribution** in `lattice_epm.rst:611`: Change "depinning universality" to "experimental observations (thermal creep)" and note that athermal EPM gives β ≈ 2-3 ### Should Fix 3. **Update mu bounds** in `lattice_epm.rst` parameter table to match code `(0.1, 1e9)` 4. **Increase TensorialEPM lattice size** in `01_epm_flow_curve.ipynb` from L=16 to L=32 ### Nice to Fix 5. Replace `assert` guards with explicit `ValueError` in `base.py` and `tensor.py` (18 instances) 6. Add convergence tests at larger L values --- ## Evidence - 61/61 tests passing (35 model + 26 kernel) - Ruff lint: clean - Mypy: no EPM-specific errors - 6/6 kernel equations verified against published literature - 10+ peer-reviewed references cross-checked