From e293e74eaedcc3391bf2e4105e74032964b09a42 Mon Sep 17 00:00:00 2001 From: Jukka Aho Date: Mon, 15 Dec 2025 06:15:49 +0200 Subject: [PATCH] feat(materials): add material field trait system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New 140-line material field trait system: - required_material_fields(): define fields required by physics (Elasticity, Thermal) - material_field_type(): infer field NamedTuple type from material's supported physics - compose_field_types(): compose multiple NamedTuple types for multi-physics - Supports Elasticity (σ, 𝔻) and Thermal (q, k) physics - Type-stable field type inference - Already integrated in JuliaFEM.jl (line 431) Provides compositional field design for material-physics coupling. --- src/materials/field_traits.jl | 140 ++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/materials/field_traits.jl diff --git a/src/materials/field_traits.jl b/src/materials/field_traits.jl new file mode 100644 index 0000000..3ae9d98 --- /dev/null +++ b/src/materials/field_traits.jl @@ -0,0 +1,140 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md + +""" +Material field trait functions for compositional field design. +""" + +using Tensors +using ..JuliaFEM: AbstractPhysics, Elasticity, Thermal + +# ============================================================================ +# MATERIAL FIELD TRAITS +# ============================================================================ + +""" + required_material_fields(physics::AbstractPhysics) -> Type{<:NamedTuple} + +Returns NamedTuple type defining material fields required by this physics. +""" +function required_material_fields end + +# ============================================================================ +# PHYSICS-SPECIFIC IMPLEMENTATIONS +# ============================================================================ + +""" + required_material_fields(::Elasticity{Dim}) where Dim + +Elasticity physics requires stress (σ) and tangent modulus (𝔻). +""" +function required_material_fields(::Elasticity{Dim}) where Dim + if Dim == 3 + return NamedTuple{ + (:σ, :𝔻), + Tuple{ + SymmetricTensor{2,3,Float64,6}, + SymmetricTensor{4,3,Float64,36} + } + } + elseif Dim == 2 + return NamedTuple{ + (:σ, :𝔻), + Tuple{ + SymmetricTensor{2,2,Float64,3}, + SymmetricTensor{4,2,Float64,9} + } + } + else + error("Elasticity{Dim} with Dim=$Dim not supported") + end +end + +""" + required_material_fields(::Thermal{Dim}) where Dim + +Thermal physics requires heat flux (q) and thermal conductivity (k). +""" +function required_material_fields(::Thermal{Dim}) where Dim + if Dim == 3 + return NamedTuple{ + (:q, :k), + Tuple{ + Vec{3,Float64}, + SymmetricTensor{2,3,Float64,6} + } + } + elseif Dim == 2 + return NamedTuple{ + (:q, :k), + Tuple{ + Vec{2,Float64}, + SymmetricTensor{2,2,Float64,3} + } + } + else + error("Thermal{Dim} with Dim=$Dim not supported") + end +end + +# ============================================================================ +# FIELD TYPE INFERENCE +# ============================================================================ + +""" + material_field_type(material::AbstractMaterial) -> Type{<:NamedTuple} + +Infer material field NamedTuple type from material's supported physics. +""" +function material_field_type(material::AbstractMaterial) + physics_tuple = supported_physics(material) + + if isempty(physics_tuple) + # No physics - empty fields + return NamedTuple{(),Tuple{}} + elseif length(physics_tuple) == 1 + # Single physics - direct mapping + return required_material_fields(physics_tuple[1]) + else + # Multiple physics - compose fields + field_types = map(p -> required_material_fields(p), physics_tuple) + return compose_field_types(field_types...) + end +end + +""" + compose_field_types(nt_types::Type{<:NamedTuple}...) -> Type{<:NamedTuple} + +Compose multiple NamedTuple types into one. +""" +@generated function compose_field_types(nt_types::Type{<:NamedTuple}...) + # Collect all field names and types + all_names = Symbol[] + all_types = Type[] + + for nt_type in nt_types.parameters + names = fieldnames(nt_type) + types = [fieldtype(nt_type, name) for name in names] + append!(all_names, names) + append!(all_types, types) + end + + # Build composed NamedTuple type + names_tuple = Tuple(all_names) + types_tuple = Tuple(all_types) + + return :(NamedTuple{$names_tuple, $types_tuple}) +end + +""" + create_zero_field(::Type{FieldType}) where {FieldType<:NamedTuple} + +Create zero-initialized field NamedTuple. +""" +@generated function create_zero_field(::Type{FieldType}) where {FieldType<:NamedTuple} + names = fieldnames(FieldType) + types = [fieldtype(FieldType, name) for name in names] + zero_values = [:(zero($T)) for T in types] + + return :(NamedTuple{$names}(($(zero_values...),))) +end