From c9f05ef49e08dba681e1df01e3cfc493ff5c9d53 Mon Sep 17 00:00:00 2001 From: petlenz Date: Sun, 20 Sep 2026 10:49:28 +0200 Subject: [PATCH] materials: build the deviatoric projector once, not per call (#77) Both generic yield-function policies constructed the rank-4 IIdev inside flow_normal_stress_derivative(), which runs once per plastic step: const tensor4 IIdev{plasticity_detail::make_IIdev()}; IIdev depends on nothing but T and Dim. Measured at -O2 -DNDEBUG, building it costs 149 ns against 2.6 ns to read a cached one, and it was worth 15-22% of a plastic step: drucker_prager_plasticity 700 ns -> 548 ns (-22%) j2_rk_plasticity(implicit_euler) 971 ns -> 759 ns (-22%) j2_rk_plasticity(sdirk3) 1119 ns -> 953 ns (-15%) j2_plasticity had always cached it in a member, which is most of why its plastic step was 3-6x cheaper than the two materials that go through the policies. This brings them to the same standard. A function-local static rather than a member: the policies are value types that get copied around, so one instance is shared by every material at that (T, Dim). Initialization is thread safe, which matters because the UMAT layer runs one context per thread; after the first call only the guard check remains, and that is inside the 2.6 ns above. Stress and tangent are unchanged -- the existing J2, Drucker-Prager and RK value assertions all still pass. --- .../materials/drucker_prager_yield_function.h | 2 +- include/numsim-materials/materials/plasticity_utils.h | 7 +++++++ include/numsim-materials/materials/yield_functions.h | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/numsim-materials/materials/drucker_prager_yield_function.h b/include/numsim-materials/materials/drucker_prager_yield_function.h index cee3365..06f59ab 100644 --- a/include/numsim-materials/materials/drucker_prager_yield_function.h +++ b/include/numsim-materials/materials/drucker_prager_yield_function.h @@ -157,7 +157,7 @@ struct drucker_prager_yield_function { /// Takes sig_dev directly (not N) to avoid cancellation error from /// reconstructing s = (N - β/3·I)·2q when q is small. tensor4 flow_normal_stress_derivative(const tensor2& sig_dev, T sqrt_j2) const { - const tensor4 IIdev{plasticity_detail::make_IIdev()}; + const auto& IIdev = plasticity_detail::cached_IIdev(); const auto j2 = sqrt_j2 * sqrt_j2; return (IIdev - tmech::otimes(sig_dev, sig_dev) / (T{2} * j2)) / (T{2} * sqrt_j2); } diff --git a/include/numsim-materials/materials/plasticity_utils.h b/include/numsim-materials/materials/plasticity_utils.h index e79f6ce..76db4a3 100644 --- a/include/numsim-materials/materials/plasticity_utils.h +++ b/include/numsim-materials/materials/plasticity_utils.h @@ -17,6 +17,13 @@ tmech::tensor make_IIdev() { return tmech::tensor{IIsym - IIvol}; } +/// The same projector, built once per (T, Dim) and shared. +template +const tmech::tensor& cached_IIdev() { + static const tmech::tensor value{make_IIdev()}; + return value; +} + /// Isotropic elastic tangent C = K*I⊗I + 2G*IIdev. /// /// The bulk term is K*I⊗I, NOT 3K*IIvol. Those are equal only at Dim = 3, diff --git a/include/numsim-materials/materials/yield_functions.h b/include/numsim-materials/materials/yield_functions.h index 700d484..a23b681 100644 --- a/include/numsim-materials/materials/yield_functions.h +++ b/include/numsim-materials/materials/yield_functions.h @@ -64,7 +64,7 @@ struct j2_yield_function { /// Takes sig_dev (not N) to match the DP interface and avoid /// needing to reconstruct s from N. tensor4 flow_normal_stress_derivative(const tensor2& sig_dev, T sig_eq) const { - const tensor4 IIdev{plasticity_detail::make_IIdev()}; + const auto& IIdev = plasticity_detail::cached_IIdev(); const tensor2 N{T{1.5} * sig_dev / sig_eq}; return (T{1.5} * IIdev - tmech::otimes(N, N)) / sig_eq; }