2. 稳态热传导与热应力顺序耦合
任务目标
实现一个二维稳态热传导与热应力的顺序耦合求解器:首先求解给定边界条件下的温度场,然后将温度场作为体积载荷,求解结构在约束下的热应力与热变形。本案例采用有限差分法离散温度场,采用有限元法(常应变三角形单元)求解热应力,形成完整的顺序耦合闭环。
物理模型与耦合流程
考虑一个边长为 L=1.0 m 的方形薄板,材料为各向同性线弹性体。温度场满足稳态热传导方程 ∇²T = 0,边界条件为:左边界 T=100°C,右边界 T=0°C,上下边界绝热。求得温度场后,将温度变化 ΔT = T(x,y) - T_ref(参考温度 T_ref=0°C)作为热载荷,在结构左边界施加固定约束(u_x = u_y = 0),求解热应力场。
顺序耦合的关键在于:温度场求解独立于应力场,但应力场依赖温度场的空间分布。本实现中温度场采用有限差分法在规则网格上求解,应力场采用同一套网格生成的三角形单元进行有限元计算,保证数据传递的一致性。
环境依赖
仅需 Python 3.8+ 及 NumPy,无需其他第三方库。代码自包含全部求解逻辑。
关键完整代码
import numpy as np
# ========== 参数定义 ==========
L = 1.0 # 板边长 (m)
nx, ny = 20, 20 # 网格数(温度场与应力场共用节点)
E = 200e9 # 弹性模量 (Pa)
nu = 0.3 # 泊松比
alpha = 1.2e-5 # 热膨胀系数 (1/°C)
T_ref = 0.0 # 参考温度 (°C)
T_left, T_right = 100.0, 0.0 # 左右边界温度
# ========== 1. 稳态温度场求解(有限差分 + 高斯-赛德尔迭代) ==========
dx = L / nx
dy = L / ny
T = np.zeros((ny + 1, nx + 1))
T[:, 0] = T_left # 左边界
T[:, -1] = T_right # 右边界
# 迭代求解拉普拉斯方程(绝热上下边界:Neumann条件)
for _ in range(20000):
T_old = T.copy()
for j in range(1, ny):
for i in range(1, nx):
T[j, i] = 0.25 * (T_old[j, i+1] + T_old[j, i-1] +
T_old[j+1, i] + T_old[j-1, i])
# 上下边界绝热(∂T/∂y = 0)
T[0, :] = T[1, :]
T[-1, :] = T[-2, :]
if np.max(np.abs(T - T_old)) < 1e-6:
break
# ========== 2. 热应力有限元求解(常应变三角形单元) ==========
# 生成节点坐标(与温度场网格一致)
n_nodes = (nx + 1) * (ny + 1)
nodes = np.zeros((n_nodes, 2))
for j in range(ny + 1):
for i in range(nx + 1):
nodes[j * (nx + 1) + i] = [i * dx, j * dy]
# 生成三角形单元(每个网格分成2个三角形)
elements = []
for j in range(ny):
for i in range(nx):
n0 = j * (nx + 1) + i
n1 = n0 + 1
n2 = n0 + (nx + 1)
n3 = n2 + 1
elements.append([n0, n1, n2]) # 左下三角形
elements.append([n1, n3, n2]) # 右上三角形
elements = np.array(elements)
n_elem = len(elements)
# 组装刚度矩阵与热载荷向量
K = np.zeros((2 * n_nodes, 2 * n_nodes))
F = np.zeros(2 * n_nodes)
# 材料矩阵(平面应力)
D = E / (1 - nu**2) * np.array([
[1, nu, 0],
[nu, 1, 0],
[0, 0, (1 - nu) / 2]
])
for e in range(n_elem):
idx = elements[e]
coords = nodes[idx] # 3x2
xe, ye = coords[:, 0], coords[:, 1]
# 形函数导数(常应变三角形)
B = np.zeros((3, 6))
area = 0.5 * ((xe[1] - xe[0]) * (ye[2] - ye[0]) -
(xe[2] - xe[0]) * (ye[1] - ye[0]))
if abs(area) < 1e-12:
continue
dN_dx = np.array([ye[1] - ye[2], ye[2] - ye[0], ye[0] - ye[1]]) / (2 * area)
dN_dy = np.array([xe[2] - xe[1], xe[0] - xe[2], xe[1] - xe[0]]) / (2 * area)
B[0, 0::2] = dN_dx
B[1, 1::2] = dN_dy
B[2, 0::2] = dN_dy
B[2, 1::2] = dN_dx
# 单元刚度矩阵
Ke = B.T @ D @ B * area
dofs = np.zeros(6, dtype=int)
for k in range(3):
dofs[2*k] = 2 * idx[k]
dofs[2*k+1] = 2 * idx[k] + 1
K[np.ix_(dofs, dofs)] += Ke
# 热载荷(等效节点力)
T_elem = np.mean(T[idx[:, 1], idx[:, 0]]) # 单元平均温度
dT = T_elem - T_ref
if abs(dT) > 1e-12:
# 热应变 ε_th = alpha * dT * [1, 1, 0]^T
eth = alpha * dT * np.array([1.0, 1.0, 0.0])
sigma_th = D @ eth
Fe = B.T @ sigma_th * area
F[dofs] -= Fe
# 施加约束:左边界节点 (x=0) 固定 ux=uy=0
constrained_dofs = []
for j in range(ny + 1):
node_id = j * (nx + 1) # x=0 的节点
constrained_dofs.extend([2 * node_id, 2 * node_id + 1])
free_dofs = np.setdiff1d(np.arange(2 * n_nodes), constrained_dofs)
# 求解位移
K_ff = K[np.ix_(free_dofs, free_dofs)]
F_f = F[free_dofs]
u_free = np.linalg.solve(K_ff, F_f)
u = np.zeros(2 * n_nodes)
u[free_dofs] = u_free
# ========== 3. 后处理:计算应力与最大位移 ==========
# 提取每个单元的平均应力
elem_stress = np.zeros((n_elem, 3))
for e in range(n_elem):
idx = elements[e]
coords = nodes[idx]
xe, ye = coords[:, 0], coords[:, 1]
area = 0.5 * ((xe[1] - xe[0]) * (ye[2] - ye[0]) -
(xe[2] - xe[0]) * (ye[1] - ye[0]))
dN_dx = np.array([ye[1] - ye[2], ye[2] - ye[0], ye[0] - ye[1]]) / (2 * area)
dN_dy = np.array([xe[2] - xe[1], xe[0] - xe[2], xe[1] - xe[0]]) / (2 * area)
B = np.zeros((3, 6))
B[0, 0::2] = dN_dx
B[1, 1::2] = dN_dy
B[2, 0::2] = dN_dy
B[2, 1::2] = dN_dx
dofs = np.zeros(6, dtype=int)
for k in range(3):
dofs[2*k] = 2 * idx[k]
dofs[2*k+1] = 2 * idx[k] + 1
u_e = u[dofs]
strain = B @ u_e
T_elem = np.mean(T[idx[:, 1], idx[:, 0]])
dT = T_elem - T_ref
eth = alpha * dT * np.array([1.0, 1.0, 0.0])
elem_stress[e] = D @ (strain - eth)
# 输出关键结果
ux = u[0::2].reshape(ny + 1, nx + 1)
uy = u[1::2].reshape(ny + 1, nx + 1)
max_disp = np.sqrt(ux**2 + uy**2).max()
max_von_mises = np.sqrt(elem_stress[:, 0]**2 - elem_stress[:, 0]*elem_stress[:, 1] +
elem_stress[:, 1]**2 + 3*elem_stress[:, 2]**2).max()
print(f"温度场迭代收敛步数: {_+1}")
print(f"中心点温度 T({L/2},{L/2}) = {T[ny//2, nx//2]:.4f} °C")
print(f"最大热变形: {max_disp:.6e} m")
print(f"最大von Mises应力: {max_von_mises:.6e} Pa")
运行与验证
运行方式:将上述代码保存为 thermal_stress_coupling.py,在命令行执行 python thermal_stress_coupling.py。程序将输出温度场迭代步数、中心点温度、最大变形与最大von Mises应力。
验证方法:
- 温度场正确性:由于左右边界温度差为100°C且上下绝热,温度场应为线性分布
T(x) = 100 - 100*x/L。中心点(0.5, 0.5)的理论温度为50°C。程序输出应接近50°C(误差<0.5°C)。 - 应力场合理性:在自由膨胀但左边界固定的条件下,板整体受热膨胀。由于温度沿x方向线性递减,板内产生压缩热应力(左侧温度高,膨胀趋势大但被约束)。最大von Mises应力应出现在左边界附近,量级约为
E*alpha*ΔT_max ≈ 200e9 * 1.2e-5 * 100 = 2.4e8 Pa(考虑约束效应后略低)。 - 位移趋势:最大位移应出现在右边界(温度最低、自由膨胀最大处),方向为x正方向,量级约为
alpha * ΔT * L ≈ 1.2e-5 * 100 * 1.0 = 1.2e-3 m。
若输出结果与上述理论估算在同一量级且趋势一致,则顺序耦合实现正确。
常见问题
- 温度场不收敛:检查迭代次数上限是否足够(当前20000次),或收敛容差是否过严(1e-6已足够)。
- 应力求解奇异:确认约束已正确施加(左边界所有节点两个自由度均固定),且刚度矩阵未出现零主元。
- 网格依赖性:增大
nx, ny可提高精度,但计算时间增加。当前20×20网格在普通PC上运行时间约数秒。