Skip to content

Heat transfer

AgentFEM supports steady conduction and implicit transient heat transfer through the same study/model/step/result lifecycle used by solid mechanics.

Current routes

  • steady heat transfer with conductivity and thermal boundary conditions;
  • implicit Euler transient heat transfer with structured increments;
  • conservative nonlinear implicit heat transfer with tabulated (k(T)) and (c_p(T)), automatic residual linearization, and atomic failed-step rollback;
  • accepted-time temperature-field capture with explicit interpolation, out-of-range policy, partition-portable persistence, and content identity;
  • thermal-field handoff to thermoelastic stress workflows;
  • common result, progress, checkpoint, and verification concepts.

Steady and constant-property implicit transient conduction on a rectangular periodic cell accept constraints.rectangular_periodic_mpc(temperature) through the ordinary model.step(...) call. It uses the same exact-MPC lowering as linear solids, including serial/MPI diagnostics and pre-assembly rejection of overlapping or ambiguous constraint providers. The constant operator lifecycle is prepared once per transient run and reused while the history/source vector changes. The same provider-owned dual recovery is available after a converged linear solve; in a thermal problem the nodal distribution is the algebraic flux dual to the homogeneous periodic temperature relation rather than a mechanical force. Structural force/work quantities are therefore attached only by solid analysis Steps. General master/slave geometry still requires a dedicated reviewed construction provider.

Typical workflow

thermal study → mesh/regions → temperature field → conductivity/capacity
              → temperature/flux/convection conditions → transient or steady step
              → temperature/flux histories → verification → optional stress handoff

Temperature history handoff

The transient step can retain the solved field as a physical-time object:

step = thermal_model.step(target=temperature, dt=10.0, steps=100)
temperature_history = step.capture_history(
    name="temperature",
    unit="K",
    interpolation="linear",
)
step.solve_result(output="temperature.xdmf")

creep_step = mechanical_model.step(
    target=displacement,
    material=creep_material,
    duration=1000.0,
    temperature=temperature_history,
)

temperature_history is not an output-frame index. It is sampled using the physical time requested by the receiving step. Creep cutback therefore restores both material state and temperature to the attempted increment's starting time. The default rejects requests beyond the recorded interval; endpoint holding requires outside="clamp" explicitly.

Saved nodal histories use coordinate-keyed physical DOF identities rather than MPI-local array numbering. The same archive can therefore be written with one partition count and restored with another, provided the mesh and function space are scientifically identical. The first format is deliberately root-gathered and compact; it is a reproducible transfer/checkpoint artifact, not yet an extreme-scale parallel field database.

Temperature-dependent sequential material data use inspectable property tables rather than anonymous interpolation functions:

young = materials.temperature_property(
    [300.0, 500.0, 700.0],
    [210e9, 190e9, 160e9],
    name="young",
    unit="Pa",
    extrapolation="constant",
)
material = constitutive.temperature_dependent_thermoelastic(
    young=young,
    poisson=0.3,
    density=7800.0,
    thermal_expansion=12e-6,
    conductivity=45.0,
    specific_heat=500.0,
)
K = mechanical_model.stiffness(displacement, temperature=temperature)

The explicit constant extrapolation is required for a bounded UFL coefficient; scalar evaluation defaults to an error outside the supplied temperature range.

When conductivity or specific heat is a TemperaturePropertyTable, the same top-level call automatically uses

\[ \frac{h(T_{n+1})-h(T_n)}{\Delta t} -\nabla\!\cdot\!\left(k(T_{n+1})\nabla T_{n+1}\right)=Q, \qquad \frac{dh}{dT}=\rho c_p(T). \]

Using the enthalpy difference rather than \(\rho c_p(T_{n+1})(T_{n+1}-T_n)\) keeps the discrete heat content consistent with the property table. Nonlinear convergence evidence, heat-balance history, checkpoint/restart, output, and MPI execution remain attached to the ordinary transient Step. A failed nonlinear increment restores both current and accepted temperature fields. Explicit C= or K= overrides are rejected on this route because they would mix two incompatible descriptions of the same physics.

The next depth is heat generated by inelastic dissipation, a larger external power-component thermal-to-creep benchmark, adaptive nonlinear thermal increments, and monolithic coupling where two-way feedback is physically necessary.

Sequential thermoelasticity and eigenstrain

Temperature does not become stress because of a field name. Declare the stress-free strain source explicitly, then let the ordinary static-solid Step lower it over the registered material regions:

from agentfem import eigenstrains

model.eigenstrain(eigenstrains.thermal(temperature))
result = model.step(target=displacement).solve_result()

For several materials, every material owns a disjoint CellRegion and the regions cover the mesh exactly once. AgentFEM assembles regional stiffness and thermal virtual work without user-written UFL measure concatenation.

The scientific result contains one physical stress S and the strain decomposition E_TOTAL, E_EIGEN, E_MECH. E remains the compatibility spelling of total infinitesimal strain. These are discontinuous projections and are not averaged across material interfaces.

When the model declares a consistent unit system, the result manifest records displacement, stress/energy-density, temperature and dimensionless-strain units explicitly. AgentFEM never infers a unit system from coefficient magnitudes.

Go deeper

Reference