Module: src/physics/materials Purpose: The rules whose violation produces memory errors, deadlocks or physically wrong results, stated as contracts with their reasons Date: 2026-08-25 (first version 2026-01-16) Revision: 2.0
The usage guide explains how to call the module. This document lists what must be true for those calls to be safe. Each contract names the code that enforces it, or states plainly that nothing does.
set_jc_function( const JcFunction * ), set_n_function( const JcFunction * ), load_bh_curve( const BhCurve * ) and set_bh_curve( const BhCurve * ) hand the object to the material permanently. The caller must never delete it. Use load_bh_curve: it also routes H, mu and dmudH to the curve, which set_bh_curve does not.
The destructors enforce this: Material::~Material deletes the J_c and n functions (cl_Material.cpp:174), and Metal::~Metal deletes the B-H curve (cl_Material_Metal.cpp:61). A second caller-side delete is a double free.
MaterialFactory::create_material, create_bh_curve and create_jc_function return caller-owned pointers. The label-based path keeps no registry; the input-deck constructor fills a label → pointer map (mMaterialsMap) for the kernel's lookup, and the factory destructor deletes nothing in either case.
| Call | Returns | Owner until set_*() | Owner after |
|---|---|---|---|
| create_material( … ) | Material * | caller | caller — always |
| create_bh_curve( … ) | BhCurve * | caller | the material, after load_bh_curve / set_bh_curve |
| create_jc_function( … ) | JcFunction * | caller | the material, after set_jc_function / set_n_function |
An Alloy owns the component Metal objects the factory built for it.
Mesh blocks, FEM kernels, DOF managers and calculators hold raw pointers to materials. Destroy materials last — after the kernel and the mesh.
Nothing enforces this; a dangling material pointer surfaces as a crash in assembly.
rho( T ) is the normal-state resistivity of the matrix. The superconducting E–J power law is rho_powerlaw( normJ, T, normB, angleNxB ). Choosing one is a modeling decision, not a convenience.
The assembly path always calls the full T-bearing overload and lets the material route on its declared dependencies (usage guide §7.3). All arguments are real: an argument order copied from an older document — ( normJ, normB, angle, T ) — compiles and silently evaluates the wrong point.
Use density( gTroom ) or ref_density() in the mass matrix, never density( T ).
BELFEM computes on the undeformed mesh, and density( T ) = ρ_ref / l(T)³ already accounts for the expansion the mesh does not undergo. calculator::MaxwellData caches the reference value once at construction.
Metal::set_RRR sets ρ₀ and, when tables are enabled, builds the lookup table. Call it once, before the material is assigned to a block. Alloy accepts its RRR only through the factory and raises an error on set_RRR.
set_RRR also rebuilds the λ spline. Because set_spline resets that property's dependency flags to T-only, set_RRR then re-registers the Kohler flags when depends( rho, normB ) is true. This preserves depends( lambda, normB ) for every pure metal, so the tool and the calculator select lambda( T, B, beta ) on that flag alone.
Most material constants default to NaN — the exceptions set in the constructor are mu (to μ₀), q (to 1) and density_correction (cl_Material.cpp:164-170). So a metal without an RRR has no ρ(T) and no λ(T). Nothing asserts that; the NaN propagates.
Metal::populate_rho_database runs when set_RRR finds depends( rho, angleBxJ ) and tables are enabled. Every rank enters it. Rank 0 evaluates every node of the tensor work mesh; the other ranks hold an element-less copy and join the projector's collective solve (Database( Mesh *, … , aProject = true ), cl_Database.cpp:57). Later constructions load <label>_RRR<n>.hdf5 from the run directory after a rank-0 format probe is broadcast (cl_Material_Metal.cpp:891).
Do not "fix" this into a distributed build.
The projector's system is small and every solver it can use assembles on the main process anyway; distributing the build would add more communication than it saves. A consequence for anyone reading the packing code: populate_rho_database( Mesh *, Vector< real > & ) packs values densely over nodes it owns, while the projector reads them back positionally (F( tElement->node( i )->index() ), cl_DatabaseProjector.cpp:260). These are one index space: Mesh assigns node->set_index( tCount++ ) walking the same container, so under single ownership slot k is node k. The owner() != rank filter is a permanent no-op under this contract, not a hook for a distributed future.
Metal::rho( T, B, beta ) and lambda( T, B, beta ) evaluate Kohler's rule directly without a table and read the table when one exists. Alloy::rho( T, B, beta ) reads the table and asserts its presence (cl_Material_Alloy.hpp:147) — an alloy queried for field dependence must have been constructed with tables enabled.
Two consequences: a Metal built with aBuildTables = false is slower per query but complete; an Alloy built that way is field-blind, and the assertion is compiled out in release builds.
The build samples a three-dimensional tensor mesh over (T, log₁₀B, β) and solves a projection. Do it once per (label, RRR) — the cache file exists so that it happens once per run directory, not once per run.
No wall-clock figures are recorded in the tree; an earlier version of this document quoted timings that had no source. Measure before planning around a number.
have( MaterialProperty ) is the only availability check. Querying an unset property dispatches through a null routing pointer.
Built-in materials set everything they list; user-defined materials have exactly what their init function sets; Magnesia has no resistivity; HastelloyC276 has no field dependence.
depends( property, dependency ) reports what the property varies with. It validates nothing.
The FEM calculator uses depends() to choose between the ( T ) and ( T, B, beta ) overloads; a caller that ignores it and passes fewer arguments than the material consumes gets the field-free value without warning. Declare dependencies truthfully — for a user-defined J_c the routing trusts them completely (usage guide §7.3).
After a material has been handed to a mesh block or a kernel, call no set_*() on it.
Materials are shared across blocks. Kernels cache material-dependent quantities, and a mid-simulation set_RRR would also try to rebuild the lookup table collectively.
A property is a function of state (T, B, β, J), never of element size, shape or orientation. Geometry belongs in the kernels.
set_user_defined_polynomial and every polyval in the module take coefficients from highest power to constant term (MATLAB order); the overloads take Cell< real > or std::vector< real >.
The factory rejects a user-defined (usermat) material registered under buffer; the label names the thin-shell φ-formulation buffer layer and resolves to Magnesia. A built-in section with that label is not checked and also resolves to Magnesia.
Construct and configure on the master thread. Read-only queries after construction are const and touch no mutable state, and are expected to be safe from OpenMP threads — but BELFEM's stated policy is that nothing internal is thread-safe (doc/coding_philosophy.md), and no test exercises concurrent material queries. Protect with #pragma omp critical when in doubt.