Files
JuliaFEM.jl/test/materials/test_material_cache.jl
T

287 lines
10 KiB
Julia
Raw Normal View History

2025-12-15 08:34:47 +02:00
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using Test
using JuliaFEM
using Tensors
@testset "Material Cache" begin
@testset "State type inference" begin
# Test that material_state_type correctly infers from traits
# Stateless material (LinearElastic)
mat_stateless = LinearElastic(E=210e9, ν=0.3)
StateType_stateless = JuliaFEM.material_state_type(mat_stateless)
@test StateType_stateless === NamedTuple{(),Tuple{}}
@test fieldnames(StateType_stateless) == ()
# Stateful material (PerfectPlasticity)
# Note: PerfectPlasticity not exported yet, so this test will be added later
# when PerfectPlasticity is integrated
end
@testset "Zero state creation" begin
# Test empty state
StateType_empty = NamedTuple{(),Tuple{}}
zero_state = JuliaFEM.create_zero_state(StateType_empty)
@test zero_state === NamedTuple()
# Test tensor state
StateType_tensor = NamedTuple{(:ε_p,), Tuple{SymmetricTensor{2,3,Float64,6}}}
zero_state = JuliaFEM.create_zero_state(StateType_tensor)
@test haskey(zero_state, :ε_p)
@test zero_state.ε_p == zero(SymmetricTensor{2,3})
# Test scalar state
StateType_scalar = NamedTuple{(,), Tuple{Float64}}
zero_state = JuliaFEM.create_zero_state(StateType_scalar)
@test haskey(zero_state, )
@test zero_state.κ == 0.0
# Test mixed state (like plasticity)
StateType_mixed = NamedTuple{(:ε_p, :α, ), Tuple{SymmetricTensor{2,3,Float64,6}, SymmetricTensor{2,3,Float64,6}, Float64}}
zero_state = JuliaFEM.create_zero_state(StateType_mixed)
@test haskey(zero_state, :ε_p)
@test haskey(zero_state, :α)
@test haskey(zero_state, )
@test zero_state.ε_p == zero(SymmetricTensor{2,3})
@test zero_state.α == zero(SymmetricTensor{2,3})
@test zero_state.κ == 0.0
end
@testset "MaterialCache construction - stateless" begin
mat = LinearElastic(E=210e9, ν=0.3)
n_ips = 8
cache = JuliaFEM.create_global_material_cache(mat, n_ips=n_ips, n_elems=10)
# Check type
@test cache isa JuliaFEM.GlobalMaterialCache{NamedTuple{(),Tuple{}}}
# Check structure (Matrix: [nips, nelems])
@test size(cache.states) == (n_ips, 10)
@test size(cache.states_old) == (n_ips, 10)
# Check initialization
for elem_id in 1:10, ip in 1:n_ips
@test cache.states[ip, elem_id] === NamedTuple()
@test cache.states_old[ip, elem_id] === NamedTuple()
end
end
@testset "MaterialCache construction - stateful" begin
# Define a simple stateful state type manually
StateType = NamedTuple{(:ε_p, ), Tuple{SymmetricTensor{2,3,Float64,6}, Float64}}
n_ips = 4
n_elems = 3
cache = JuliaFEM.GlobalMaterialCache{StateType}(n_ips, n_elems)
# Check type
@test cache isa JuliaFEM.GlobalMaterialCache{StateType}
# Check structure (Matrix: [nips, nelems])
@test size(cache.states) == (n_ips, n_elems)
@test size(cache.states_old) == (n_ips, n_elems)
# Check initialization to zeros
for elem_id in 1:n_elems, ip in 1:n_ips
state = cache.states[ip, elem_id]
@test haskey(state, :ε_p)
@test haskey(state, )
@test state.ε_p == zero(SymmetricTensor{2,3})
@test state.κ == 0.0
state_old = cache.states_old[ip, elem_id]
@test haskey(state_old, :ε_p)
@test haskey(state_old, )
@test state_old.ε_p == zero(SymmetricTensor{2,3})
@test state_old.κ == 0.0
end
end
@testset "State access functions" begin
StateType = NamedTuple{(:ε_p, ), Tuple{SymmetricTensor{2,3,Float64,6}, Float64}}
n_ips = 4
n_elems = 2
cache = JuliaFEM.GlobalMaterialCache{StateType}(n_ips, n_elems)
# Test get_state (requires ip and elem_id)
ip = 2
elem_id = 1
state = JuliaFEM.get_state(cache, ip, elem_id)
@test state isa NamedTuple
@test haskey(state, :ε_p)
@test haskey(state, )
# Test get_old_state
state_old = JuliaFEM.get_old_state(cache, ip, elem_id)
@test state_old isa NamedTuple
@test haskey(state_old, :ε_p)
@test haskey(state_old, )
# Test set_state!
new_ε_p = SymmetricTensor{2,3}((0.001, 0.0, 0.0, 0.0, 0.0, 0.0))
new_κ = 0.5
new_state = (ε_p=new_ε_p, κ=new_κ)
JuliaFEM.set_state!(cache, ip, elem_id, new_state)
state_updated = JuliaFEM.get_state(cache, ip, elem_id)
@test state_updated.ε_p == new_ε_p
@test state_updated.κ == new_κ
# Verify old state unchanged
state_old_check = JuliaFEM.get_old_state(cache, ip, elem_id)
@test state_old_check.ε_p == zero(SymmetricTensor{2,3})
@test state_old_check.κ == 0.0
end
@testset "update_cache! - time stepping" begin
StateType = NamedTuple{(,), Tuple{Float64}}
n_ips = 3
n_elems = 1
cache = JuliaFEM.GlobalMaterialCache{StateType}(n_ips, n_elems)
elem_id = 1
# Set some current states
JuliaFEM.set_state!(cache, 1, elem_id, (κ=0.1,))
JuliaFEM.set_state!(cache, 2, elem_id, (κ=0.2,))
JuliaFEM.set_state!(cache, 3, elem_id, (κ=0.3,))
# Old states should still be zero
@test JuliaFEM.get_old_state(cache, 1, elem_id).κ == 0.0
@test JuliaFEM.get_old_state(cache, 2, elem_id).κ == 0.0
@test JuliaFEM.get_old_state(cache, 3, elem_id).κ == 0.0
# Update cache (copy current → old)
JuliaFEM.update_cache!(cache)
# Now old states should match current
@test JuliaFEM.get_old_state(cache, 1, elem_id).κ == 0.1
@test JuliaFEM.get_old_state(cache, 2, elem_id).κ == 0.2
@test JuliaFEM.get_old_state(cache, 3, elem_id).κ == 0.3
# Current states unchanged
@test JuliaFEM.get_state(cache, 1, elem_id).κ == 0.1
@test JuliaFEM.get_state(cache, 2, elem_id).κ == 0.2
@test JuliaFEM.get_state(cache, 3, elem_id).κ == 0.3
# Modify current states
JuliaFEM.set_state!(cache, 1, elem_id, (κ=0.15,))
JuliaFEM.set_state!(cache, 2, elem_id, (κ=0.25,))
# Old states should still be from previous step
@test JuliaFEM.get_old_state(cache, 1, elem_id).κ == 0.1
@test JuliaFEM.get_old_state(cache, 2, elem_id).κ == 0.2
# Current states are new
@test JuliaFEM.get_state(cache, 1, elem_id).κ == 0.15
@test JuliaFEM.get_state(cache, 2, elem_id).κ == 0.25
end
@testset "reset_cache!" begin
StateType = NamedTuple{(:ε_p, ), Tuple{SymmetricTensor{2,3,Float64,6}, Float64}}
n_ips = 2
n_elems = 1
cache = JuliaFEM.GlobalMaterialCache{StateType}(n_ips, n_elems)
elem_id = 1
# Set some non-zero states
ε_p = SymmetricTensor{2,3}((0.01, 0.0, 0.0, 0.0, 0.0, 0.0))
JuliaFEM.set_state!(cache, 1, elem_id, (ε_p=ε_p, κ=0.5))
JuliaFEM.set_state!(cache, 2, elem_id, (ε_p=ε_p, κ=0.8))
# Update to move to old
JuliaFEM.update_cache!(cache)
# Verify non-zero
@test JuliaFEM.get_state(cache, 1, elem_id).κ != 0.0
@test JuliaFEM.get_old_state(cache, 1, elem_id).κ != 0.0
# Reset
JuliaFEM.reset_cache!(cache)
# All should be zero
for ip in 1:n_ips
state = JuliaFEM.get_state(cache, ip, elem_id)
state_old = JuliaFEM.get_old_state(cache, ip, elem_id)
@test state.ε_p == zero(SymmetricTensor{2,3})
@test state.κ == 0.0
@test state_old.ε_p == zero(SymmetricTensor{2,3})
@test state_old.κ == 0.0
end
end
@testset "State variable helpers" begin
# Create a state matching plasticity structure
StateType = NamedTuple{(:ε_p, :α, ), Tuple{SymmetricTensor{2,3,Float64,6}, SymmetricTensor{2,3,Float64,6}, Float64}}
ε_p_val = SymmetricTensor{2,3}((0.001, 0.0, 0.0, 0.0, 0.0, 0.0))
α_val = SymmetricTensor{2,3}((100e6, 0.0, 0.0, 0.0, 0.0, 0.0))
κ_val = 0.01
state = (ε_p=ε_p_val, α=α_val, κ=κ_val)
# Test get_state_variable
extracted_ε_p = JuliaFEM.get_state_variable(state, PlasticStrain)
@test extracted_ε_p == ε_p_val
extracted_α = JuliaFEM.get_state_variable(state, Backstress)
@test extracted_α == α_val
extracted_κ = JuliaFEM.get_state_variable(state, EquivalentPlasticStrain)
@test extracted_κ == κ_val
# Test set_state_variable (immutable update)
new_κ_val = 0.02
new_state = JuliaFEM.set_state_variable(state, EquivalentPlasticStrain, new_κ_val)
# Original state unchanged (immutable)
@test state.κ == κ_val
# New state has updated value
@test new_state.κ == new_κ_val
# Other values unchanged
@test new_state.ε_p == ε_p_val
@test new_state.α == α_val
end
@testset "Type stability" begin
# Verify that cache operations are type-stable
mat = LinearElastic(E=210e9, ν=0.3)
cache = JuliaFEM.create_global_material_cache(mat, n_ips=4, n_elems=2)
# get_state should be type-stable (requires ip and elem_id)
@inferred JuliaFEM.get_state(cache, 1, 1)
@inferred JuliaFEM.get_old_state(cache, 1, 1)
# For stateful cache
StateType = NamedTuple{(,), Tuple{Float64}}
cache_stateful = JuliaFEM.GlobalMaterialCache{StateType}(4, 2)
@inferred JuliaFEM.get_state(cache_stateful, 1, 1)
@inferred JuliaFEM.get_old_state(cache_stateful, 1, 1)
end
@testset "Integration with material traits" begin
# Verify that the cache system integrates correctly with material traits
mat = LinearElastic(E=210e9, ν=0.3)
# Material declares no state variables
vars = required_state_variables(mat)
@test vars === ()
# Cache should be empty
StateType = JuliaFEM.material_state_type(mat)
@test StateType === NamedTuple{(),Tuple{}}
# Created cache should be stateless
cache = JuliaFEM.create_global_material_cache(mat, n_ips=2, n_elems=3)
@test cache isa JuliaFEM.GlobalMaterialCache{NamedTuple{(),Tuple{}}}
@test JuliaFEM.get_state(cache, 1, 1) === NamedTuple()
end
end