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)) # VickersE = 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)) # dampingk = 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
- Linear elasticity and heat conduction — the scalar FEM paths these materials plug into.
