RasmahRasmah

Materials and Multiphysics Properties

Overview

Rasmah ships a built-in material library for multiphysics simulation — linear elasticity, heat conduction, fluid flow, strength/fracture, and electromagnetics — plus a display Texture for rendering. Everything is SI units, every value is compiled from permissively-licensed handbooks, and each material records its provenance.

There are four material forms, all subtypes of AbstractMaterial:

  • Material — an isotropic solid (scalar properties).
  • AnisotropicMaterial — a full 6×6 Voigt stiffness tensor + 3×3 conductivity tensor.
  • CompositeMaterial / Laminate — fibre composites and stacked laminates, homogenized to concrete properties on demand.
  • FluidMaterial — a Newtonian fluid (viscosity + bulk modulus).
using Rasmah
using LinearAlgebra

println(steel)                      # Material(Steel (A36); E=2.0e11 Pa, …)
println(water)                      # FluidMaterial(Water; ρ=998.0 kg/m³, μ=0.001002 Pa·s)
 Downloading artifact: vtkjs
     Failure artifact: vtkjs
 Downloading artifact: vtkjs
Material(Steel (A36); E=2.0e11 Pa, ν=0.29, ρ=7850.0 kg/m³, k=51.9 W/(m·K), σ=6.99e6 S/m)
FluidMaterial(Water; ρ=998.0 kg/m³, μ=0.001002 Pa·s)

The built-in library

materials() returns the full solid library (71 entries); material(name) looks one up by name — exact match first, then a family-prefix match — and grades lists the grades of a material family:

println(length(materials()), " solid materials, ", length(fluids()), " fluids")

println(material(:steel).name)          # Steel (A36)
println(material("carbon").name)        # prefix match → Carbon Fiber (UD)
println(material("aluminum 6061-t6") === aluminum)

# grade-aware lookup
println(material("steel"; grade="1040").name)
println(length(grades("steel")), " steel grades")
71 solid materials, 11 fluids
Steel (A36)
Carbon Fiber (UD)
true
Steel (1040)
3 steel grades

fluids() / fluid(name) do the same for the 11 built-in fluids:

println(fluid(:air).name)
println(fluid("glycerin") === glycerin)
Air
true

Property accessors

Every scalar property has an accessor. Here are the mechanical ones for steel:

println("E  = ", youngs_modulus(steel))       # Pa
println("ν  = ", poisson_ratio(steel))        # dimensionless
println("ρ  = ", density(steel))              # kg/m³
println("σy = ", yield_strength(steel))       # Pa
println("σu = ", ultimate_strength(steel))    # Pa
println("ef = ", elongation(steel))           # fraction
println("H  = ", hardening_modulus(steel))    # Pa
println("σf = ", fatigue_strength(steel))     # Pa
println("KIc= ", fracture_toughness(steel))   # Pa·√m
println("HV = ", hardness(steel))             # Vickers
E  = 2.0e11
ν  = 0.29
ρ  = 7850.0
σy = 2.5e8
σu = 4.0e8
ef = 0.2
H  = 2.0e9
σf = 1.9e8
KIc= 1.2e8
HV = 120.0

Thermal, electromagnetic, phase, and optical properties use the same pattern:

println("k   = ", thermal_conductivity(copper))     # W/(m·K)
println("α   = ", thermal_expansion(steel))         # 1/K
println("c   = ", specific_heat(aluminum))          # J/(kg·K)
println("σ   = ", electrical_conductivity(copper))  # S/m
println("μr  = ", relative_permeability(steel))     # ferromagnetic
println("ε   = ", emissivity(concrete))
println("εr  = ", relative_permittivity(silicon))
println("n   = ", refractive_index(glass))
println("Tm  = ", melting_point(copper))            # K
println("Tc  = ", curie_temperature(nickel))        # K
println("d33 = ", piezoelectric_d33(pzt))           # m/V
println("d31 = ", piezoelectric_d31(pzt))           # m/V
println("d15 = ", piezoelectric_d15(pzt))           # m/V
println("Ms  = ", saturation_magnetization(iron))   # A/m
println("λs  = ", magnetostriction(nickel))
println("p   = ", pyroelectric_coefficient(pzt))    # C/(m²·K)
println("S   = ", seebeck_coefficient(bismuth_telluride))   # V/K
println("tanδ= ", dielectric_loss_tangent(ptfe))
println("Lf  = ", latent_heat_fusion(aluminum))     # J/kg
println("η   = ", loss_factor(rubber))              # damping
k   = 401.0
α   = 1.17e-5
c   = 896.0
σ   = 5.8e7
μr  = 100.0
ε   = 0.9
εr  = 11.7
n   = 1.52
Tm  = 1358.0
Tc  = 627.0
d33 = 3.74e-10
d31 = -1.71e-10
d15 = 5.84e-10
Ms  = 1.71e6
λs  = -3.4e-5
p   = -0.00025
S   = -0.0002
tanδ= 0.0002
Lf  = 397000.0
η   = 0.1

Sentinel values follow the module’s convention: 0.0 means “not applicable” (not piezoelectric, not ferromagnetic, lossless), and Inf means “not defined” (no melting point for a decomposing material, relative_permittivity/refractive_index for conductors). hardness may be missing when not defined for a material family (e.g. elastomers).

println(melting_point(wood))            # Inf   (decomposes)
println(relative_permittivity(steel))   # Inf   (conductor)
println(piezoelectric_d33(steel))       # 0.0   (not piezoelectric)
println(saturation_magnetization(aluminum))  # 0.0 (non-ferromagnetic)
println(ismissing(hardness(rubber)))    # true  (not defined)
Inf
Inf
0.0
0.0
true

Derived isotropic constants

Shear/bulk modulus, thermal diffusivity, sound speed, and acoustic impedance are derived from the stored scalars (not stored):

E, ν = youngs_modulus(steel), poisson_ratio(steel)
println(shear_modulus(steel) ≈ E / (2 * (1 + ν)))            # G
println(bulk_modulus(steel) ≈ E / (3 * (1 - 2ν)))            # K
println(thermal_diffusivity(steel) ≈
    thermal_conductivity(steel) / (density(steel) * specific_heat(steel)))
println(sound_speed(steel) ≈
    sqrt((bulk_modulus(steel) + 4 * shear_modulus(steel) / 3) / density(steel)))
println(acoustic_impedance(steel) ≈ density(steel) * sound_speed(steel))
true
true
true
true
true

Display texture

Every material carries a Texture (sRGB base color plus a compact PBR-style description) usable directly as the color keyword of vtk:

println(color(steel))                       # (0.53, 0.55, 0.58)
println(texture(steel).metallic)            # 1.0
println(texture(glass).opacity)             # 0.5
(0.53, 0.55, 0.58)
1.0
0.5

Grades and custom materials

with_grade derives a new grade from a base material (inheriting every property not overridden); override changes any property (including relative_uncertainty); material(name; …) does the override inline; and the Material constructor builds one from scratch:

g = with_grade(steel; name="Steel (custom)", grade="X99", yield_strength=9.0e8)
println(grade(g), " σy=", yield_strength(g), " E=", youngs_modulus(g))

o = override(steel; yield_strength=5.0e8, relative_uncertainty=0.03)
println(yield_strength(o), "  δ=", relative_uncertainty(o))

s = material("steel"; yield_strength=7.0e8)
println(yield_strength(s), "  ν=", poisson_ratio(s))

c = Material(; name="my-alloy", youngs_modulus=1.2e11, poisson_ratio=0.3, density=8000.0)
println(c)
X99 σy=9.0e8 E=2.0e11
5.0e8  δ=0.03
7.0e8  ν=0.29
Material(my-alloy; E=1.2e11 Pa, ν=0.3, ρ=8000.0 kg/m³, k=0.0 W/(m·K), σ=0.0 S/m)

Uncertainty

relative_uncertainty is the coefficient of variation; property_range expands it to a (lo, hi) band. When the uncertainty is not documented in the handbooks it is missing, and the band degenerates to (v, v):

println(ismissing(relative_uncertainty(steel)))     # undocumented → missing
println(property_range(steel, youngs_modulus))      # (v, v)

u = override(steel; relative_uncertainty=0.05)
println(property_range(u, youngs_modulus))          # (1.9e11, 2.1e11)
true
(2.0e11, 2.0e11)
(1.9e11, 2.1e11)

Temperature-dependent properties

A material can carry a temperature_data table of piecewise-linear curves. TempSeries (alias Series) is the callable interpolating series, with endpoint clamping outside the tabulated range:

s = TempSeries([300.0, 400.0, 600.0], [100.0, 200.0, 400.0])
println(s(500.0))       # 300.0 (midpoint)
println(s(200.0))       # 100.0 (clamped)

# temperature-aware accessors interpolate the table, else fall back to nominal
println(thermal_conductivity(steel, 500.0))   # 45.55 (steel carries a table)
println(thermal_conductivity(copper, 500.0))  # 401.0 (no table → nominal)
300.0
100.0
45.55
386.0

Attach new tables with with_temperature_data, and query generically with value_at / property_curve:

m = with_temperature_data(steel; thermal_conductivity=TempSeries([300.0, 1000.0], [60.0, 20.0]))
println(thermal_conductivity(m, 650.0))       # 40.0

println(value_at(steel, :thermal_conductivity, 500.0))
k = property_curve(steel, :thermal_conductivity)
println(k(500.0), "  ", k(300.0))
40.0
45.55
45.55  51.9

Field- and frequency-dependent models

Function-valued physical models are computed from the stored scalars:

# B–H curve (saturation model for ferromagnets)
bh = bh_curve(iron)
println(magnetization(iron, 0.0), "  ", magnetization(iron, 1.0e7) ≈ saturation_magnetization(iron))
println(bh(0.0), "  ", magnetic_flux_density(iron, 1.0e6))

# magnetostrictive strain (quadratic saturation)
println(magnetostrictive_strain(nickel, saturation_magnetization(nickel)) ≈ 1.5 * magnetostriction(nickel))

# 3×6 piezoelectric strain matrix (6mm class)
d = piezoelectric_strain_matrix(pzt)
println(size(d), "  ", d[3, 3] ≈ piezoelectric_d33(pzt))

# spectral emissivity (gray body by default; table-driven otherwise)
println(spectral_emissivity(steel, 1.0e-6))
tbl = Series([1.0e-6, 2.0e-6], [0.3, 0.6])
println(spectral_emissivity(steel, tbl, 1.5e-6))

# Debye single-relaxation permittivity
println(debye_permittivity(80.0, 2.0, 1.0e-11, 0.0))    # ε∞ + (εs−ε∞) at f=0
println(debye_permittivity(80.0, 2.0, 1.0e-11, 1.0e12) < 80.0)
0.0  true
0.0  3.4054864364913358
true
(3, 6)  true
0.25
0.45
80.0
true

Traceability

property_source / property_sources map each property group to its handbook; each built-in material records its primary reference in source:

println(source(steel))
println(property_source(:youngs_modulus))
println(property_source(:refractive_index))
println(haskey(property_sources(), :dynamic_viscosity))
ASM Metals Handbook (ASTM A36)
ASM Metals Handbook; CRC Handbook
refractiveindex.info (CC-BY 4.0)
true

Fluids

FluidMaterial adds Newtonian-viscosity, compressibility, surface-tension, and phase properties to the shared scalar set:

println(dynamic_viscosity(water))         # μ  Pa·s
println(kinematic_viscosity(water))       # ν = μ/ρ  m²/s
println(bulk_modulus(water))              # K  Pa
println(surface_tension(water))           # N/m
println(boiling_point(ethanol))           # K
println(latent_heat_vaporization(water))  # J/kg
println(specific_heat_ratio(air))         # γ (gases)
println(prandtl_number(water))            # μ c / k
println(sound_speed(air), "  ", sound_speed(water))
println(acoustic_impedance(water))
0.001002
1.0040080160320641e-6
2.15e9
0.0728
351.0
2.257e6
1.4
7.010648829431439
343.4242264831475  1467.7563207952705
1.46482080815368e6

A fluid is an AbstractMaterial but not a Material, and a custom fluid is built with the FluidMaterial constructor:

println(water isa AbstractMaterial)
println(!(water isa Material))

f = FluidMaterial(; name="x", density=1.0e3, dynamic_viscosity=0.1)
println(kinematic_viscosity(f))
true
true
0.0001

Anisotropic materials

orthotropic_material assembles the 6×6 Voigt compliance from engineering constants and inverts it; transversely_isotropic_material is the single-axis specialization. stiffness / compliance_tensor / engineering_constants round-trip any material:

o = orthotropic_material(;
    name="ortho", E1=1.0e11, E2=2.0e10, E3=2.0e10,
    ν12=0.3, ν13=0.3, ν23=0.4, G12=5.0e9, G13=5.0e9, G23=3.0e9, density=1500.0,
)
println(o isa AnisotropicMaterial)
C = elasticity_matrix(o)
println(size(C), "  symmetric: ", C ≈ transpose(C))

ec = engineering_constants(o)
println(round(ec.E1), "  ", round(ec.G12))

t = transversely_isotropic_material(; E1=1.0e11, E2=2.0e10, ν12=0.3, ν23=0.4, G12=5.0e9)
println(elasticity_matrix(t) ≈ transpose(elasticity_matrix(t)))
true
(6, 6)  symmetric: true
1.0e11  5.0e9
true

stiffness derives the 6×6 tensor of any material — isotropic (from E, ν), anisotropic (stored), or composite (homogenized first) — and compliance_tensor is its inverse:

println(stiffness(steel) ≈ elasticity_matrix(steel))
println(compliance_tensor(steel) ≈ inv(Matrix(stiffness(steel))))
true
true

Composites and laminates

fiber_composite builds a two-constituent composite (matrix + fibre + fibre volume fraction, oriented unidirectionally or in-plane random), Ply is a single oriented ply, and Laminate stacks plies:

cf = fiber_composite(epoxy, carbon_fiber, 0.6)
println(cf)
println(density(cf) ≈ 0.6 * density(carbon_fiber) + 0.4 * density(epoxy))

ud = homogenize(cf)                              # unidirectional → anisotropic
println(ud isa AnisotropicMaterial)

rf = fiber_composite(epoxy, carbon_fiber, 0.3; orientation=:random)
println(homogenize(rf) isa Material)             # random → isotropic
println(youngs_modulus(homogenize(rf)) > youngs_modulus(epoxy))
CompositeMaterial(fiber composite; Carbon Fiber (UD)/Epoxy, Vf=0.6, unidirectional)
true
true
true
true

laminate_ABD returns the classical-lamination-theory in-plane (A), coupling (B), and bending (D) stiffness matrices; homogenize reduces a laminate to its effective material:

l = Laminate([Ply(ud; angle=0.0, thickness=0.125), Ply(ud; angle=90.0, thickness=0.125)];
             name="cross-ply")
A, B, D = laminate_ABD(l)
println(size(A), "  A ≈ Aᵀ: ", A ≈ transpose(A), "  balanced [0/90]: ", A[1, 1] ≈ A[2, 2])

eff = homogenize(l)
println(eff isa AnisotropicMaterial)
(3, 3)  A ≈ Aᵀ: true  balanced [0/90]: true
true

unidirectional_fiber_composite and random_fiber_composite are the direct micromechanics entry points (Chamis rule-of-mixtures / Cox–Krenchel).

FEM integration

A material slots straight into the physics interface: name the physics, pass the mesh and the material, and solve picks the PDE, boundary conditions, and solver. The material’s properties (stiffness, thermal_conductivity, …) drive the assembly, so solve(Elasticity(), m, aluminum) and solve(HeatConduction(), m, copper) need nothing but the material:

m = tetrahedralize_box(1.0, 1.0, 1.0, 2, 2, 2)
n = size(m.nodes, 2)

fixed = Int[]
for i in 1:n
    m.nodes[1, i] ≈ 0.0 && append!(fixed, 3i - 2, 3i - 1, 3i)
end
forces = zeros(3n)
for i in 1:n
    m.nodes[1, i] ≈ 1.0 && (forces[3i - 2] = 1.0)
end

u = solve(Elasticity(), m, aluminum; fixed=fixed, loads=forces)
σ = von_mises_stress(m, ustrip(u), aluminum)
println("compliance     = ", round(compliance(m, aluminum, fixed, forces); digits=6))
println(size(ustrip(u)), "  ", size(σ), "  all σ ≥ 0: ", all(σ .>= 0))
compliance     = 0.0
(81,)  (48,)  all σ ≥ 0: true

Heat conduction works the same way (solve(HeatConduction(), m, copper)), and an AnisotropicMaterial assembles directly from its 6×6 C / 3×3 K (3D only):

T = solve(HeatConduction(), m, copper; fixed=[i for i in 1:n if m.nodes[1, i] ≈ 0.0],
          loads=zeros(n))
println("temperature field length: ", length(ustrip(T)))

am = AnisotropicMaterial(;
    name="k", stiffness=elasticity_matrix(o),
    thermal_conductivity=[10.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0], density=1500.0,
)
Kh = assemble_matrix(heat_form(FESpace(m, ReferenceFE(3, 1)), thermal_conductivity(am)))
println(Kh ≈ transpose(Kh))
temperature field length: 27
true

The complete library

Every built-in solid and fluid is a named constant (all Materials / FluidMaterials, in SI units). The solids, grouped by family:

metals = [steel, stainless_steel, aluminum, titanium, copper, brass, cast_iron,
          magnesium, tungsten, lead, gold, silver, nickel, inconel]
grades = [steel_1040, steel_4340, stainless_316, aluminum_7075, aluminum_2024,
          titanium_grade2, copper_beryllium, brass_freecut]
ceramics = [concrete, glass, silicon, silicon_carbide, alumina, zirconia,
            boron_carbide, silicon_nitride, fused_quartz, granite, marble]
polymers = [pla, abs_plastic, nylon, rubber, epoxy, pmma, polycarbonate, peek_polymer,
            ptfe, polypropylene, pvc, hdpe, pet, pom, polyimide]
composites = [carbon_fiber, wood]
special = [molybdenum, cobalt, platinum, zinc, tin, tantalum, niobium, beryllium, iron, nitinol]
electronic = [pzt, quartz, barium_titanate, pvdf, zinc_oxide, bismuth_telluride,
              lead_telluride, silicon_germanium, gaas, gan, germanium]

n_solids = length(metals) + length(grades) + length(ceramics) + length(polymers) +
           length(composites) + length(special) + length(electronic)
println(n_solids, " built-in solids")
71 built-in solids

And the fluids:

all_fluids = [water, seawater, air, helium, glycerin, ethanol, motor_oil, mercury,
              nitrogen, oxygen, carbon_dioxide]
println(length(all_fluids), " built-in fluids")
11 built-in fluids

These are the same entries materials() and fluids() return — look up any of them by name with material(...) / fluid(...), e.g. material(:zirconia) or fluid("motor oil").

Next steps