About
There are three main approaches to solving thermoelastic coupling problems: Unidirectional(weak) coupling, Bidirectional(strong) coupling, and Partitioned iterative coupling.
In this context, we deal with one-way coupling, where one field is solved first and its results are then passed to the other field.
In the case of thermoelastic analysis, the heat problem is solved first, and the resulting temperature distribution is then used as input for the structural analysis.
Heat Conduct Equation
Strong Form
\nabla \cdot (k \nabla T) = Q \quad \text{in } \Omega\\
\begin{cases}
T = T_D & \text{on } \Gamma_D \quad \text{(Dirichlet BC)} \\
k \dfrac{\partial T}{\partial n} = h (T - T_\mathrm{env}) & \text{on } \Gamma_R \quad \text{(Robin BC)}
\end{cases}Weak Form
\int_\Omega k \nabla T \cdot \nabla v d\Omega
+ \int_{\Gamma_R} h T v d\Gamma
=
\int_\Omega Q v d\Omega
+ \int_{\Gamma_R} h T_\mathrm{env} v d\GammaHeat Diffusion (Conduct) Term
a(u, v) = \int_{\Omega} k \nabla u \cdot \nabla v d\Omega \quad \Longleftrightarrow \quad -\nabla \cdot (k \nabla T)Internal Heat Generation
l_Q(v) = \int_{\Omega} Q v d\Omega \quad \Longleftrightarrow \quad +QRobin Boundary Condition
k \frac{\partial T}{\partial n} = h (T - T_{\mathrm{env}})\int_{\Gamma_h} h T v d\Gamma = \int_{\Gamma_h} h T_{\mathrm{env}} v d\GammaHeat Radiation (Robin BC, Boundary Integral)
a_h(u, v) = \int_{\Gamma_h} h u v d\Gamma \quad \Longleftrightarrow \quad +hTHeat Exchange with Ambient Air (Robin BC, boundary integral)
l_h(v) = \int_{\Gamma_h} h T_{\mathrm{env}} v d\Gamma \quad \Longleftrightarrow \quad +hT_{\mathrm{env}}Dirichlet Boundary Condition
T = T_{\mathrm{env}} \quad \text{on } \Gamma_DImplementation with Scikit-fem
# Parameters
k = 10.0
h = 5.0
T_env = 300.0
T_src = T_env + 300.0
@skfem.BilinearForm
def heat_form(u, v, w):
return w.k * dot(grad(u), grad(v))
@skfem.LinearForm
def heat_load(v, w):
# w.Q = 0
# without Internal heat generation
return w.Q * v
# return 0.0 * v
@skfem.BilinearForm
def robin_form(u, v, w):
# Surface integral (heat radiation term)
return h * u * v
@skfem.LinearForm
def robin_load(v, w):
# Temperature difference from outside temperature
return h * T_env * v
# T_dofs_top = T_basis.with_elements(lambda x: x[2] == z_len)
T_facets_top = mesh.facets_satisfying(lambda x: abs(x[2] - z_len) < 1e-9)
T_facet_basis_top = skfem.FacetBasis(mesh, element, facets=T_facets_top)
T_dofs_bottom = T_basis.get_dofs(lambda x: x[2] == 0.0)
T_A = skfem.asm(heat_form, T_basis, k=k)
T_b = skfem.asm(heat_load, T_basis, Q=0.0)
T_A += skfem.asm(robin_form, T_facet_basis_top, h=h)
T_b += skfem.asm(robin_load, T_facet_basis_top, h=h, T_env=T_env)
T_sol = skfem.solve(
*skfem.condense(
T_A, T_b, D=T_dofs_bottom, x=np.full(T_dofs_bottom.N, T_src)
)
)
print("Computed temperature range:", T_sol.min(), "→", T_sol.max())

Structural Analysis with Temperature Distribution
Thermal Strain–induced Load
The stress–strain relationship (Hooke’s law) for a linear isotropic elastic material, including the effect of thermal strain due to a temperature change ΔT, can be expressed as follows:
\sigma = \lambda\mathrm{tr}(\varepsilon - \varepsilon_T)I + 2\mu(\varepsilon - \varepsilon_T)\\
where\space ε_T=\alpha \Delta T I\alpha is linear thermal expansion coefficient, and ε_T is isotropic thermal strain.
Substituting stress on Force equilibrium below,
-\nabla\cdot\sigma = 0
the following equation that depends on temperature is obtained.
-[\nabla\cdot(\lambda \mathrm{tr}(\varepsilon) I + 2\mu\varepsilon)
- \nabla\cdot\big((3\lambda + 2\mu)\alpha\Delta T I\big)] = 0Implementation with Scikit-fem
E = 210e3
nu = 0.3
lam, mu = lame_parameters(E, nu)
T_std = 300.0
F_facet_ids = mesh.facets_satisfying(
lambda x: np.isclose(x[2], z_len)
)
fbasis = skfem.FacetBasis(mesh, element, facets=F_facet_ids)
total_force = 100.0 # [N]
@skfem.Functional
def surface_measure(w):
return 1.0
pressure = total_force / surface_measure.assemble(fbasis)
s_element = skfem.ElementVector(element)
s_basis = skfem.Basis(mesh, s_element)
@skfem.LinearForm
def l_comp(v, w):
return pressure * dot(w.n, v)
@skfem.LinearForm
def thermal_load(v, w):
ΔT = w.ΔT
# return -3 * w.lam * w.alpha * ΔT * div(v)
return -(3*w.lam + 2*w.mu) * w.alpha * ΔT * div(v)
ΔT_field = T_basis.interpolate(T_sol - T_std)
w = dict(alpha=1e-5, ΔT=ΔT_field, lam=lam, mu=mu)
s_A = linear_elasticity(lam, mu).assemble(s_basis)
s_b = skfem.asm(thermal_load, s_basis, **w)
s_b += l_comp.assemble(fbasis)
u = skfem.solve(*skfem.condense(s_A, s_b, D=s_basis.get_dofs()))