mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-12 06:22:00 +00:00
287 lines
10 KiB
Julia
287 lines
10 KiB
Julia
|
|
# 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
|