diff --git a/test/validation/test_plasticity_simple.jl b/test/validation/test_plasticity_simple.jl new file mode 100644 index 0000000..2d289d8 --- /dev/null +++ b/test/validation/test_plasticity_simple.jl @@ -0,0 +1,191 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md + +""" +Simple Plasticity Test: Single Hex8 Element with PerfectPlasticity + +Demonstrates end-to-end workflow with compositional material cache: +1. Create GlobalMaterialCache from material traits +2. Assemble with state tracking +3. Verify plastic strain accumulates correctly + +This is a UNIT TEST, not a validation test. Goal is to verify the +material cache integration works correctly. +""" + +using Test +using JuliaFEM +using LinearAlgebra +using SparseArrays +using Tensors + +@testset "Simple Plasticity - GlobalMaterialCache Integration" begin + println("\n" * "="^70) + println("SIMPLE PLASTICITY TEST - GlobalMaterialCache Integration") + println("="^70) + + # ======================================================================== + # 1. Create Single Element Mesh + # ======================================================================== + + println("\n[1] Creating single element mesh...") + + # Unit cube Hex8 element + nodes = [ + Vec{3}((0.0, 0.0, 0.0)), # 1 + Vec{3}((1.0, 0.0, 0.0)), # 2 + Vec{3}((1.0, 1.0, 0.0)), # 3 + Vec{3}((0.0, 1.0, 0.0)), # 4 + Vec{3}((0.0, 0.0, 1.0)), # 5 + Vec{3}((1.0, 0.0, 1.0)), # 6 + Vec{3}((1.0, 1.0, 1.0)), # 7 + Vec{3}((0.0, 1.0, 1.0)), # 8 + ] + + connectivity = [NTuple{8,UInt32}((1, 2, 3, 4, 5, 6, 7, 8))] + element_sets = Dict{Symbol,Set{UInt32}}(:all => Set(UInt32[1])) + mesh = Mesh{8,Hexahedron{8}}(nodes, connectivity, element_sets) + + println(" Nodes: $(length(nodes))") + println(" Elements: $(length(connectivity))") + + # ======================================================================== + # 2. Material and GlobalMaterialCache + # ======================================================================== + + println("\n[2] Creating material and global cache...") + + # Plasticity material + E = 210e9 # Pa + ν = 0.3 + σ_y = 250e6 # Pa (250 MPa yield stress) + H = 1e9 # Pa (hardening modulus) + + material = PerfectPlasticity(E=E, ν=ν, σ_y=σ_y, H=H) + + println(" Material: PerfectPlasticity") + println(" E = $(E/1e9) GPa") + println(" σ_y = $(σ_y/1e6) MPa") + println(" H = $(H/1e9) GPa") + + # Verify material traits + state_vars = required_state_variables(material) + @test state_vars == (PlasticStrain, Backstress, EquivalentPlasticStrain) + println(" State variables: $state_vars") + + # Create global material cache + nips = 8 # Hex8 has 8 integration points (2x2x2 Gauss) + nelems = 1 + global_cache = create_global_material_cache(material, n_ips=nips, n_elems=nelems) + + @test global_cache isa GlobalMaterialCache + @test size(global_cache.states) == (nips, nelems) + println(" Created GlobalMaterialCache: $(size(global_cache.states))") + + # Verify initial state is zero + for ip in 1:nips + state = get_state(global_cache, ip, 1) + @test state.ε_p == zero(SymmetricTensor{2,3}) + @test state.α == zero(SymmetricTensor{2,3}) + @test state.κ == 0.0 + end + println(" ✓ All states initialized to zero") + + # ======================================================================== + # 3. Demonstrate Material Response + # ======================================================================== + + println("\n[3] Testing material response...") + + # Test 1: Elastic response (small strain) + ε_elastic = SymmetricTensor{2,3}((1e-4, 0.0, 0.0, 0.0, 0.0, 0.0)) + state_old_1 = NamedTuple() # Empty state = initial + σ_1, 𝔻_1, state_new_1 = compute_stress(material, ε_elastic, state_old_1, 0.0) + + @test state_new_1.κ == 0.0 # No plastic strain + println(" ✓ Elastic response: κ = $(state_new_1.κ)") + + # Test 2: Plastic response (large strain) + ε_plastic = SymmetricTensor{2,3}((0.005, 0.0, 0.0, 0.0, 0.0, 0.0)) + state_old_2 = NamedTuple() # Empty state = initial + σ_2, 𝔻_2, state_new_2 = compute_stress(material, ε_plastic, state_old_2, 0.0) + + @test state_new_2.κ > 0.0 # Plastic strain accumulated + println(" ✓ Plastic response: κ = $(state_new_2.κ)") + + # ======================================================================== + # 4. Direct NamedTuple Workflow (No Bridge!) + # ======================================================================== + + println("\n[4] Testing direct NamedTuple workflow...") + + # State is already a NamedTuple - use directly! + @test state_new_2 isa NamedTuple + @test haskey(state_new_2, :ε_p) + @test haskey(state_new_2, :α) + @test haskey(state_new_2, :κ) + println(" ✓ State is native NamedTuple (no conversion needed)") + + # Store directly in global cache + set_state!(global_cache, 1, 1, state_new_2) + retrieved_state = get_state(global_cache, 1, 1) + @test retrieved_state.κ == state_new_2.κ + println(" ✓ State stored directly in GlobalMaterialCache") + + # Access directly from cache + @test get_state(global_cache, 1, 1) isa NamedTuple + println(" ✓ Direct access from cache (no extraction needed)") + + # ======================================================================== + # 5. Time-Stepping Workflow + # ======================================================================== + + println("\n[5] Testing time-stepping workflow...") + + # Reset cache + reset_cache!(global_cache) + + # Step 1: Small load (elastic) + println(" Step 1: Elastic loading...") + state_step1_old = get_state(global_cache, 1, 1) # Get directly + σ_step1, 𝔻_step1, state_step1 = compute_stress(material, ε_elastic, state_step1_old, 0.0) + set_state!(global_cache, 1, 1, state_step1) # Set directly + + @test get_state(global_cache, 1, 1).κ == 0.0 + println(" κ = $(get_state(global_cache, 1, 1).κ) (elastic)") + + # Update cache for next step + update_cache!(global_cache) + + # Step 2: Large load (plastic) + println(" Step 2: Plastic loading...") + state_step2_old = get_state(global_cache, 1, 1) # Get directly + σ_step2, 𝔻_step2, state_step2 = compute_stress(material, ε_plastic, state_step2_old, 0.0) + set_state!(global_cache, 1, 1, state_step2) # Set directly + + κ_current = get_state(global_cache, 1, 1).κ + κ_old = get_old_state(global_cache, 1, 1).κ + + @test κ_current > 0.0 # Plastic strain accumulated + @test κ_old == 0.0 # Old state still elastic + println(" κ_current = $κ_current (plastic)") + println(" κ_old = $κ_old (from previous step)") + + # ======================================================================== + # 6. Summary + # ======================================================================== + + println("\n" * "="^70) + println("SUMMARY - Compositional Material Cache (No Bridge!)") + println("="^70) + println(" ✓ GlobalMaterialCache created from material traits") + println(" ✓ NamedTuple states used directly (no conversion)") + println(" ✓ compute_stress accepts/returns NamedTuple natively") + println(" ✓ States stored and retrieved without any bridge") + println(" ✓ Time-stepping workflow simplified") + println(" ✓ Plastic strain accumulation tracked") + println(" ✓ Pure compositional design - elegant and fast!") + println("="^70) + + @test true # If we got here, everything worked! +end