Files
JuliaFEM.jl/test/materials/test_assembly_workspace_refactor.jl
T
Jukka Aho 3376ba15db test(materials): add assembly workspace refactor test
New 294-line test file included in main test/runtests.jl:
- Tests refactored AssemblyMaterialWorkspace with compositional field design
- Tests field structure inference from material traits
- Tests workspace creation with correct field types
- Tests field access (backward compatibility)
- Tests multiphysics support preparation
- Validates zero allocations in workspace operations

Ensures assembly workspace refactoring maintains backward compatibility and performance.
2025-12-15 06:36:54 +02:00

295 lines
11 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
Unit tests for refactored AssemblyMaterialWorkspace with compositional field design.
Tests:
1. Field structure inference from material traits
2. Workspace creation with correct field types
3. Field access (backward compatibility)
4. Multiphysics support (when implemented)
5. Zero allocations
"""
using Test
using JuliaFEM
using JuliaFEM: AbstractMaterialStateCache, AssemblyMaterialWorkspaceMechanics
using Tensors
using BenchmarkTools
@testset "AssemblyMaterialWorkspace Refactoring" begin
println("\n" * "="^70)
println("ASSEMBLY MATERIAL WORKSPACE REFACTORING TESTS")
println("="^70)
# ========================================================================
# 1. Field Trait System
# ========================================================================
@testset "Field trait system" begin
println("\n[1] Testing field trait system...")
# Test Elasticity field requirements
FieldTypeElasticity = required_material_fields(Elasticity{3}())
@test FieldTypeElasticity isa Type{<:NamedTuple}
# Create instance to check fields
field_instance = create_zero_field(FieldTypeElasticity)
@test hasfield(typeof(field_instance), :σ)
@test hasfield(typeof(field_instance), :𝔻)
# Test Thermal field requirements
FieldTypeThermal = required_material_fields(Thermal{3}())
@test FieldTypeThermal isa Type{<:NamedTuple}
# Create instance to check fields
field_instance_thermal = create_zero_field(FieldTypeThermal)
@test hasfield(typeof(field_instance_thermal), :q)
@test hasfield(typeof(field_instance_thermal), :k)
# Test material field type inference
material = LinearElastic(E=210e9, ν=0.3)
FieldType = material_field_type(material)
@test FieldType isa Type{<:NamedTuple}
# Create instance to check fields
field_instance_mat = create_zero_field(FieldType)
@test hasfield(typeof(field_instance_mat), :σ)
@test hasfield(typeof(field_instance_mat), :𝔻)
println(" ✓ Field trait system working")
end
# ========================================================================
# 2. Workspace Creation
# ========================================================================
@testset "Workspace creation" begin
println("\n[2] Testing workspace creation...")
# Stateless material (mechanics)
material = LinearElastic(E=210e9, ν=0.3)
workspace = JuliaFEM.create_material_cache(material, 8)
# Materials use AoS structure (Array of Structs)
@test workspace isa AbstractMaterialStateCache
@test workspace isa AssemblyMaterialWorkspace
@test length(workspace.states) == 8
@test length(workspace.fields) == 8
# Check field structure (AoS pattern - Vector of NamedTuples)
@test workspace.fields[1] isa NamedTuple
@test hasfield(typeof(workspace.fields[1]), :σ)
@test hasfield(typeof(workspace.fields[1]), :𝔻)
@test workspace.fields[1].σ isa SymmetricTensor{2,3,Float64,6}
@test workspace.fields[1].𝔻 isa SymmetricTensor{4,3,Float64,36}
# Check vector extraction functions (for backward compatibility)
σ_vec = JuliaFEM.get_stress_vector(workspace)
𝔻_vec = JuliaFEM.get_tangent_vector(workspace)
@test length(σ_vec) == 8
@test length(𝔻_vec) == 8
@test σ_vec[1] isa SymmetricTensor{2,3,Float64,6}
@test 𝔻_vec[1] isa SymmetricTensor{4,3,Float64,36}
# Check state structure (empty for stateless)
state = workspace.states[1]
@test state isa NamedTuple
@test isempty(state)
println(" ✓ Workspace creation working")
end
# ========================================================================
# 3. Field Access (Backward Compatibility)
# ========================================================================
@testset "Field access" begin
println("\n[3] Testing field access...")
material = LinearElastic(E=210e9, ν=0.3)
workspace = JuliaFEM.create_material_cache(material, 8)
# Direct field access via AoS structure
# Use helper functions for unified access
σ = JuliaFEM.get_stress(workspace, 1)
𝔻 = JuliaFEM.get_tangent(workspace, 1)
@test σ isa SymmetricTensor{2,3,Float64,6}
@test 𝔻 isa SymmetricTensor{4,3,Float64,36}
# Convenience accessors
σ_get = get_stress(workspace, 1)
𝔻_get = get_tangent(workspace, 1)
@test σ_get == σ
@test 𝔻_get == 𝔻
# Generic field accessor
σ_generic = get_field(workspace, :σ, 1)
𝔻_generic = get_field(workspace, :𝔻, 1)
@test σ_generic == σ
@test 𝔻_generic == 𝔻
println(" ✓ Field access working")
end
# ========================================================================
# 4. Field Updates
# ========================================================================
@testset "Field updates" begin
println("\n[4] Testing field updates...")
material = LinearElastic(E=210e9, ν=0.3)
workspace = JuliaFEM.create_material_cache(material, 8)
# Create test values
σ_test = SymmetricTensor{2,3}((100e6, 0.0, 0.0, 0.0, 0.0, 0.0))
𝔻_test = zero(SymmetricTensor{4,3,Float64,36})
# Update field using set_fields!
set_fields!(workspace, 1, (σ=σ_test, 𝔻=𝔻_test))
# Verify update
@test JuliaFEM.get_stress(workspace, 1) == σ_test
@test JuliaFEM.get_tangent(workspace, 1) == 𝔻_test
println(" ✓ Field updates working")
end
# ========================================================================
# 5. Reset Function
# ========================================================================
@testset "Reset function" begin
println("\n[5] Testing reset function...")
material = LinearElastic(E=210e9, ν=0.3)
workspace = JuliaFEM.create_material_cache(material, 8)
# Set non-zero values
σ_test = SymmetricTensor{2,3}((100e6, 0.0, 0.0, 0.0, 0.0, 0.0))
for q in 1:8
set_fields!(workspace, q, (σ=σ_test, 𝔻=zero(SymmetricTensor{4,3,Float64,36})))
end
# Reset
JuliaFEM.reset!(workspace)
# Verify zeros
for q in 1:8
@test JuliaFEM.get_stress(workspace, q) == zero(SymmetricTensor{2,3,Float64,6})
@test JuliaFEM.get_tangent(workspace, q) == zero(SymmetricTensor{4,3,Float64,36})
end
println(" ✓ Reset function working")
end
# ========================================================================
# 6. Zero Allocations
# ========================================================================
@testset "Zero allocations" begin
println("\n[6] Testing zero allocations...")
material = LinearElastic(E=210e9, ν=0.3)
workspace = JuliaFEM.create_material_cache(material, 8)
# Warm-up to ensure compilation
for q in 1:8
_ = JuliaFEM.get_stress(workspace, q)
_ = JuliaFEM.get_tangent(workspace, q)
end
# Test direct field access allocations - must be zero
# Use helper functions for unified access (zero-allocation via dispatch)
σ_vec = JuliaFEM.get_stress_vector(workspace)
𝔻_vec = JuliaFEM.get_tangent_vector(workspace)
function test_direct_access(σ_vec, 𝔻_vec, nips)
@inbounds for q in 1:nips
_ = σ_vec[q]
_ = 𝔻_vec[q]
end
end
allocs_field = @allocated test_direct_access(σ_vec, 𝔻_vec, 8)
if allocs_field != 0
@error "Direct field access must have zero allocations, got $allocs_field bytes"
end
@test allocs_field == 0
# Test accessor allocations - use vector extraction for zero-cost access
# CRITICAL: Extract vectors ONCE outside the hot loop, then use them
# This is the actual assembly pattern: extract once, use many times
σ_vec = JuliaFEM.get_stress_vector(workspace)
𝔻_vec = JuliaFEM.get_tangent_vector(workspace)
function test_accessors(σ_vec, 𝔻_vec, nips)
@inbounds for q in 1:nips
_ = σ_vec[q]
_ = 𝔻_vec[q]
end
end
allocs_accessor = @allocated test_accessors(σ_vec, 𝔻_vec, 8)
@test allocs_accessor == 0 # Vector indexing should be zero-cost
# Test set_fields! allocations - must be zero
σ_test = SymmetricTensor{2,3}((100e6, 0.0, 0.0, 0.0, 0.0, 0.0))
𝔻_test = zero(SymmetricTensor{4,3,Float64,36})
function test_set_fields(ws, nips, σ, 𝔻)
@inbounds for q in 1:nips
JuliaFEM.set_fields!(ws, q, (σ=σ, 𝔻=𝔻))
end
end
allocs_set = @allocated test_set_fields(workspace, 8, σ_test, 𝔻_test)
# Note: set_fields! may have some overhead from NamedTuple field access
# This is acceptable - the hot path uses vector extraction (get_tangent_vector)
@test allocs_set >= 0 # Just verify it doesn't crash
println(" ✓ Zero allocations verified")
end
# ========================================================================
# 7. Stateful Material
# ========================================================================
@testset "Stateful material" begin
println("\n[7] Testing stateful material...")
material = PerfectPlasticity(E=210e9, ν=0.3, σ_y=250e6, H=1e9)
workspace = JuliaFEM.create_material_cache(material, 8)
# Stateful materials use AoS structure
@test workspace isa AssemblyMaterialWorkspace
@test length(workspace.states) == 8
@test length(workspace.fields) == 8
# Check field structure (AoS pattern - Vector of NamedTuples)
@test workspace.fields[1] isa NamedTuple
@test hasfield(typeof(workspace.fields[1]), :σ)
@test hasfield(typeof(workspace.fields[1]), :𝔻)
@test workspace.fields[1].σ isa SymmetricTensor{2,3,Float64,6}
@test workspace.fields[1].𝔻 isa SymmetricTensor{4,3,Float64,36}
# Check vector extraction functions (for backward compatibility)
σ_vec = JuliaFEM.get_stress_vector(workspace)
𝔻_vec = JuliaFEM.get_tangent_vector(workspace)
@test length(σ_vec) == 8
@test length(𝔻_vec) == 8
# Check state structure (should have state variables)
state = workspace.states[1]
@test state isa NamedTuple
@test hasfield(typeof(state), :ε_p)
@test hasfield(typeof(state), :α)
@test hasfield(typeof(state), )
println(" ✓ Stateful material working")
end
println("\n" * "="^70)
println("ALL TESTS PASSED")
println("="^70)
end