CALPHAD方法
目录
1. CALPHAD核心思想
1.1 什么是CALPHAD?
CALPHAD = CAL culation of PH ase Diagrams(相图计算)
核心理念:
┌─────────────────────────────────────────┐
│ 在给定的温度、压力、成分条件下, │
│ 系统会自发趋向于吉布斯自由能最小的状态 │
└─────────────────────────────────────────┘
1.2 热力学基础
吉布斯自由能判据
判断相稳定性的金标准:
G(T, P, X) = H - T·S
其中:
- G: 吉布斯自由能 [J/mol]
- H: 焓 [J/mol]
- T: 温度 [K]
- S: 熵 [J/(mol·K)]
- X: 成分 [摩尔分数]
平衡条件
对于多相系统:
1. 化学势相等(相平衡):
μᵢᵅ = μᵢᵝ = μᵢᵞ ... (对所有组分i)
2. 机械平衡:
Pᵅ = Pᵝ = Pᵞ
3. 热平衡:
Tᵅ = Tᵝ = Tᵞ
4. 总吉布斯能最小:
G_total = Σ(nφ × Gφ) = minimum
1.3 CALPHAD工作流程
输入条件
热力学数据库
吉布斯能模型
优化问题
求解器
平衡状态
相分数 + 成分
T, P, X
TDB文件
G = G_ref + G_xs
min G_total
Newton-Raphson
2. 核心计算公式
2.1 相的吉布斯能模型
基本表达式
Gφ(T, X) = G_ref(T) + G_ideal(T, X) + G_xs(T, X) + G_mag(T)
└──┬──┘ └────┬────┘ └───┬───┘ └──┬──┘
参考态 理想混合熵 超额项 磁性贡献
详细公式
1. 参考态吉布斯能
G_ref(T) = a + b·T + c·T·ln(T) + d·T² + e·T³ + f/T
这些系数 (a,b,c,d,e,f) 从热力学数据库中获取
2. 理想混合熵
G_ideal = R·T·Σ(xᵢ·ln(xᵢ))
其中:
- R = 8.314 J/(mol·K) (气体常数)
- xᵢ: 组分i的摩尔分数
3. 超额吉布斯能(Redlich-Kister模型)
G_xs = Σᵢ Σⱼ₍ⱼ>ᵢ₎ xᵢ·xⱼ·Σₙ Lⁿᵢⱼ·(xᵢ - xⱼ)ⁿ
其中 Lⁿᵢⱼ 是相互作用参数:
Lⁿᵢⱼ = a + b·T + c·T·ln(T) + ...
2.2 多相平衡计算
目标函数
问题:给定 T, P, X_total,求各相的平衡分数和成分
优化目标:
min G_total = Σφ (nφ · Gφ(T, P, Xφ))
约束条件:
1. 质量守恒:Σφ (nφ · xᵢφ) = Xᵢ_total
2. 相分数归一:Σφ nφ = 1
3. 成分归一:Σᵢ xᵢφ = 1 (每个相)
4. 非负约束:nφ ≥ 0, xᵢφ ≥ 0
化学势计算
化学势(判断相稳定性的关键):
μᵢφ = ∂Gφ/∂nᵢφ |T,P,nⱼ≠ᵢ
平衡时:μᵢᵅ = μᵢᵝ (所有相、所有组分)
2.3 相稳定性判据
相φ稳定 ⟺ Gφ < G_single_phase
驱动力:ΔG = G_mix - Σ(xᵢ · Gᵢ_pure)
- ΔG < 0: 混合稳定(形成固溶体)
- ΔG > 0: 混合不稳定(相分离)
3. 手工计算示例
3.1 问题描述
系统:Fe-C 二元合金
成分:Fe-2wt%C (约 8.7at%C)
温度:1000°C (1273K)
压力:1 atm (101325 Pa)
候选相:FCC (奥氏体) 和 Graphite (石墨)
3.2 步骤1:成分转换
从质量百分比转换为摩尔分数:
输入:
w_C = 2 wt%
w_Fe = 98 wt%
原子量:
M_C = 12.01 g/mol
M_Fe = 55.845 g/mol
摩尔数:
n_C = 2 / 12.01 = 0.1665 mol
n_Fe = 98 / 55.845 = 1.7545 mol
n_total = 0.1665 + 1.7545 = 1.921 mol
摩尔分数:
x_C = 0.1665 / 1.921 = 0.0867 ≈ 8.7 at%
x_Fe = 1.7545 / 1.921 = 0.9133 ≈ 91.3 at%
3.3 步骤2:计算FCC相的吉布斯能
参考态能量(简化值)
G_Fe^FCC(1273K) = -10000 J/mol (假设值)
G_C^FCC(1273K) = +20000 J/mol (假设值,C在FCC中不稳定)
理想混合熵
G_ideal^FCC = R·T·[x_Fe·ln(x_Fe) + x_C·ln(x_C)]
= 8.314 × 1273 × [0.9133·ln(0.9133) + 0.0867·ln(0.0867)]
= 10583 × [0.9133×(-0.0906) + 0.0867×(-2.446)]
= 10583 × [-0.0828 - 0.212]
= 10583 × (-0.295)
= -3122 J/mol
超额项(简化)
假设 Fe-C 相互作用参数:
L_Fe,C^FCC = -20000 + 10·T = -20000 + 10×1273 = -7270 J/mol
G_xs^FCC = x_Fe · x_C · L_Fe,C^FCC
= 0.9133 × 0.0867 × (-7270)
= -575 J/mol
FCC总吉布斯能
G^FCC = x_Fe·G_Fe^FCC + x_C·G_C^FCC + G_ideal^FCC + G_xs^FCC
= 0.9133×(-10000) + 0.0867×20000 + (-3122) + (-575)
= -9133 + 1734 - 3122 - 575
= -11096 J/mol
3.4 步骤3:计算石墨的吉布斯能
纯石墨在1273K:
G^Graphite = 0 J/mol (定义为参考态)
但实际上需要考虑:
G^Graphite = G_C^Graphite(1273K) ≈ -1000 J/mol (假设值)
3.5 步骤4:判断相稳定性
比较不同状态的总吉布斯能:
方案A:全部为FCC
G_total^A = 1 × G^FCC = -11096 J/mol
方案B:FCC + 石墨两相共存(假设平衡成分)
假设FCC中含2at%C,石墨析出
G_total^B = n_FCC × G^FCC(x_C=0.02) + n_Graphite × G^Graphite
(需要通过化学势相等条件求解 n_FCC 和 n_Graphite)
结论:
如果 G_total^B < G_total^A,则两相共存
如果 G_total^A < G_total^B,则单相FCC稳定
3.6 步骤5:化学势相等(两相平衡)
平衡条件:μ_C^FCC = μ_C^Graphite
化学势计算:
μ_C^FCC = ∂G^FCC/∂n_C = G_C^FCC + R·T·ln(x_C) + (超额项导数)
μ_C^Graphite = G_C^Graphite ≈ -1000 J/mol
求解:
R·T·ln(x_C^eq) + G_C^FCC + ... = G_C^Graphite
假设结果:
x_C^eq ≈ 0.02 (2 at%C 在FCC中的固溶度)
相分数(质量守恒):
n_FCC × 0.02 + n_Graphite × 1.0 = 0.0867
n_FCC + n_Graphite = 1
求解得:
n_FCC = 0.932 (93.2%)
n_Graphite = 0.068 (6.8%)
3.7 完整结果
┌─────────────────────────────────────────┐
│ Fe-2wt%C 在 1000°C 的平衡状态 │
├─────────────────────────────────────────┤
│ 相 摩尔分数 成分 │
│ FCC 93.2% Fe-91.3at%C │
│ Graphite 6.8% 100at%C │
├─────────────────────────────────────────┤
│ 总吉布斯能: -11500 J/mol (示例值) │
└─────────────────────────────────────────┘
4. pycalphad使用指南
4.1 安装
bash
# 方法1:使用pip
pip install pycalphad
# 方法2:使用conda
conda install -c conda-forge pycalphad
# 检查安装
python -c "import pycalphad; print(pycalphad.__version__)"
4.2 基本数据结构
热力学数据库 (TDB文件)
python
from pycalphad import Database
# 加载数据库
db = Database('steel_database.tdb')
# 查看数据库内容
print(f"可用元素: {list(db.elements)}")
print(f"可用相: {list(db.phases.keys())}")
# 示例输出:
# 可用元素: ['FE', 'C', 'CR', 'NI', 'MO', 'VA']
# 可用相: ['LIQUID', 'FCC_A1', 'BCC_A2', 'M23C6', 'CEMENTITE']
组分定义
python
# 定义组分(必须包含VA=空位)
components = ['FE', 'C', 'CR', 'NI', 'VA']
# 注意:
# 1. 元素必须大写
# 2. 必须包含 VA(空位)用于间隙相
# 3. 基体元素(如Fe)可以不在条件中设置
4.3 完整计算示例:Fe-C-Cr三元系统
python
import numpy as np
import pandas as pd
from pycalphad import Database, equilibrium, variables as v
import matplotlib.pyplot as plt
# ============================================
# 示例1:单点计算
# ============================================
def example_single_point():
"""计算Fe-0.3C-12Cr在950°C的相平衡"""
# 1. 加载数据库
db = Database('steel.tdb')
# 2. 定义组分和相
comps = ['FE', 'C', 'CR', 'VA']
phases = ['FCC_A1', 'BCC_A2', 'M23C6', 'CEMENTITE']
# 3. 成分转换(wt% → at%)
def wt_to_mole_fraction(comp_wt):
"""将质量百分比转换为摩尔分数"""
atomic_weights = {'C': 12.01, 'CR': 51.996, 'FE': 55.845}
# 计算摩尔数
moles = {}
for el, wt in comp_wt.items():
moles[el] = wt / atomic_weights[el]
# 归一化
total = sum(moles.values())
return {el: mol/total for el, mol in moles.items()}
# 输入成分 (wt%)
comp_wt = {
'C': 0.3,
'CR': 12.0,
'FE': 87.7 # 100 - 0.3 - 12.0
}
# 转换为摩尔分数
comp_mole = wt_to_mole_fraction(comp_wt)
print("成分转换结果:")
print(f" 质量百分比: C={comp_wt['C']:.2f}%, Cr={comp_wt['CR']:.2f}%, Fe={comp_wt['FE']:.2f}%")
print(f" 摩尔分数: C={comp_mole['C']:.4f}, Cr={comp_mole['CR']:.4f}, Fe={comp_mole['FE']:.4f}")
# 4. 设置热力学条件
conditions = {
v.T: 950 + 273.15, # 温度 [K]
v.P: 101325, # 压力 [Pa]
v.N: 1.0, # 总摩尔数 [mol]
v.X('C'): comp_mole['C'], # C的摩尔分数
v.X('CR'): comp_mole['CR'] # Cr的摩尔分数
# Fe的摩尔分数自动确定为 1 - X(C) - X(CR)
}
print("\n热力学条件:")
print(f" 温度: {conditions[v.T] - 273.15:.1f}°C")
print(f" 压力: {conditions[v.P]/1e5:.1f} bar")
print(f" X(C): {conditions[v.X('C')]:.4f}")
print(f" X(CR): {conditions[v.X('CR')]:.4f}")
print(f" X(FE): {1 - conditions[v.X('C')] - conditions[v.X('CR')]:.4f} (自动)")
# 5. 执行相平衡计算
print("\n开始计算...")
eq = equilibrium(db, comps, phases, conditions, verbose=True)
# 6. 提取结果
print("\n" + "="*60)
print("计算结果:")
print("="*60)
# 提取相名和相分数
phase_names = eq.Phase.values.flatten()
phase_fractions = eq.NP.values.flatten()
# 只显示稳定相(分数 > 1e-6)
stable_phases = {}
for name, frac in zip(phase_names, phase_fractions):
if name and isinstance(name, (str, bytes)) and not np.isnan(frac) and frac > 1e-6:
if isinstance(name, bytes):
name = name.decode('utf-8')
name = name.strip()
stable_phases[name] = frac
# 打印相分数
print("\n稳定相及其摩尔分数:")
for phase, fraction in sorted(stable_phases.items(), key=lambda x: x[1], reverse=True):
print(f" {phase:15s}: {fraction:8.5f} ({fraction*100:6.2f}%)")
# 验证总和
total = sum(stable_phases.values())
print(f"\n相分数总和: {total:.6f} {'✓' if abs(total - 1.0) < 0.001 else '✗ 警告:不为1'}")
# 7. 提取各相的化学势
print("\n化学势 (J/mol):")
mu_data = eq.MU.values
print(f" μ(C): {mu_data[0,0,0,0]:.2f}")
print(f" μ(CR): {mu_data[0,0,0,1]:.2f}")
print(f" μ(FE): {mu_data[0,0,0,2]:.2f}")
# 8. 提取各相的成分
print("\n各相成分:")
for phase in stable_phases.keys():
print(f"\n {phase}:")
# 这里需要更复杂的代码来提取各相成分
# 简化处理:只显示主相
if phase == 'FCC_A1':
print(f" 主要为奥氏体固溶体")
return eq, stable_phases
# ============================================
# 示例2:批量计算(温度系列)
# ============================================
def example_temperature_series():
"""计算Fe-0.3C-12Cr在不同温度下的相分数"""
db = Database('steel.tdb')
comps = ['FE', 'C', 'CR', 'VA']
phases = ['FCC_A1', 'BCC_A2', 'M23C6', 'CEMENTITE']
# 温度范围
temperatures = np.linspace(400, 1200, 20) # 400-1200°C, 20个点
# 成分(摩尔分数)
x_C = 0.015
x_CR = 0.12
# 存储结果
results = {phase: [] for phase in phases}
print("批量计算:温度系列")
print(f"成分: Fe-{x_C*100:.2f}at%C-{x_CR*100:.2f}at%Cr")
print(f"温度范围: {temperatures[0]:.0f}-{temperatures[-1]:.0f}°C")
print(f"计算点数: {len(temperatures)}\n")
for T in temperatures:
conditions = {
v.T: T + 273.15,
v.P: 101325,
v.N: 1.0,
v.X('C'): x_C,
v.X('CR'): x_CR
}
eq = equilibrium(db, comps, phases, conditions, verbose=False)
# 提取相分数
phase_array = eq.Phase.values.flatten()
np_array = eq.NP.values.flatten()
phase_fracs = {phase: 0.0 for phase in phases}
for name, frac in zip(phase_array, np_array):
if name and not np.isnan(frac) and frac > 1e-10:
if isinstance(name, bytes):
name = name.decode('utf-8')
name = name.strip()
if name in phase_fracs:
phase_fracs[name] += frac
for phase in phases:
results[phase].append(phase_fracs[phase])
print(f"T={T:6.1f}°C: ", end='')
stable = [f"{p}={f:.3f}" for p, f in phase_fracs.items() if f > 0.01]
print(', '.join(stable) if stable else "No stable phases")
# 绘图
plt.figure(figsize=(10, 6))
for phase in phases:
if max(results[phase]) > 0.01: # 只绘制有显著分数的相
plt.plot(temperatures, results[phase], marker='o', label=phase)
plt.xlabel('Temperature (°C)', fontsize=12)
plt.ylabel('Phase Fraction (mole)', fontsize=12)
plt.title('Phase Fraction vs Temperature\nFe-0.3C-12Cr', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_fraction_vs_T.png', dpi=300)
print("\n图表已保存: phase_fraction_vs_T.png")
return temperatures, results
# ============================================
# 示例3:多成分批量计算
# ============================================
def example_batch_compositions():
"""批量计算多个成分"""
db = Database('steel.tdb')
comps = ['FE', 'C', 'CR', 'NI', 'VA']
phases = ['FCC_A1', 'BCC_A2', 'M23C6']
# 定义成分列表(wt%)
compositions = [
{'C': 0.2, 'CR': 10.0, 'NI': 8.0},
{'C': 0.3, 'CR': 12.0, 'NI': 9.0},
{'C': 0.4, 'CR': 15.0, 'NI': 10.0},
{'C': 0.5, 'CR': 18.0, 'NI': 12.0},
]
temperature = 950 # °C
print(f"批量计算:多成分在 {temperature}°C")
print("="*80)
results_list = []
for i, comp_wt in enumerate(compositions, 1):
print(f"\n成分 #{i}: C={comp_wt['C']:.1f}%, Cr={comp_wt['CR']:.1f}%, Ni={comp_wt['NI']:.1f}%")
# 转换为摩尔分数
atomic_weights = {'C': 12.01, 'CR': 51.996, 'NI': 58.69, 'FE': 55.845}
comp_wt['FE'] = 100 - sum(comp_wt.values())
moles = {el: wt/atomic_weights[el] for el, wt in comp_wt.items()}
total = sum(moles.values())
comp_mole = {el: mol/total for el, mol in moles.items()}
# 设置条件
conditions = {
v.T: temperature + 273.15,
v.P: 101325,
v.N: 1.0,
v.X('C'): comp_mole['C'],
v.X('CR'): comp_mole['CR'],
v.X('NI'): comp_mole['NI']
}
# 计算
eq = equilibrium(db, comps, phases, conditions, verbose=False)
# 提取结果
phase_array = eq.Phase.values.flatten()
np_array = eq.NP.values.flatten()
phase_fracs = {}
for name, frac in zip(phase_array, np_array):
if name and not np.isnan(frac) and frac > 1e-6:
if isinstance(name, bytes):
name = name.decode('utf-8')
name = name.strip()
if name not in phase_fracs:
phase_fracs[name] = 0.0
phase_fracs[name] += frac
# 打印结果
print(f" 稳定相:")
for phase, frac in sorted(phase_fracs.items(), key=lambda x: x[1], reverse=True):
print(f" {phase:15s}: {frac*100:6.2f}%")
# 保存结果
result = {'Comp_ID': i, **comp_wt, **phase_fracs}
results_list.append(result)
# 转换为DataFrame
df = pd.DataFrame(results_list)
print("\n" + "="*80)
print("汇总结果:")
print(df.to_string(index=False))
# 保存到CSV
df.to_csv('batch_results.csv', index=False)
print("\n结果已保存: batch_results.csv")
return df
# ============================================
# 运行示例
# ============================================
if __name__ == "__main__":
print("CALPHAD示例程序\n")
print("="*80)
print("示例1: 单点计算")
print("="*80)
eq, stable_phases = example_single_point()
print("\n\n")
print("="*80)
print("示例2: 温度系列计算")
print("="*80)
input("按Enter键继续...")
temperatures, results = example_temperature_series()
print("\n\n")
print("="*80)
print("示例3: 批量成分计算")
print("="*80)
input("按Enter键继续...")
df = example_batch_compositions()
print("\n所有示例完成!")
4.4 输入输出详解
输入数据结构
python
# ============================================
# 输入1: 数据库对象
# ============================================
db = Database('steel.tdb')
# 类型: pycalphad.io.database.Database
# 内容: 包含所有热力学参数
# ============================================
# 输入2: 组分列表
# ============================================
comps = ['FE', 'C', 'CR', 'NI', 'VA']
# 类型: List[str]
# 注意:
# - 元素符号必须大写
# - 必须包含VA(空位)
# - 基体元素可以不在条件中指定
# ============================================
# 输入3: 相列表
# ============================================
phases = ['FCC_A1', 'BCC_A2', 'M23C6', 'LIQUID']
# 类型: List[str]
# 注意:
# - 相名必须与数据库中定义一致
# - 只考虑列表中的相(计算速度更快)
# ============================================
# 输入4: 热力学条件
# ============================================
conditions = {
v.T: 1223.15, # 温度 [K], 必须 > 0
v.P: 101325, # 压力 [Pa], 默认1atm
v.N: 1.0, # 总物质的量 [mol]
v.X('C'): 0.015, # C的摩尔分数, [0, 1]
v.X('CR'): 0.12, # Cr的摩尔分数, [0, 1]
# Fe的摩尔分数 = 1 - X(C) - X(CR), 自动计算
}
# 类型: Dict[StateVariable, float]
# 约束:
# - Σ X(i) < 1 (基体元素自动补足)
# - T > 298.15 K (通常)
# - P > 0 Pa
输出数据结构
python
# ============================================
# 执行计算
# ============================================
eq = equilibrium(db, comps, phases, conditions)
# ============================================
# 输出: xarray.Dataset对象
# ============================================
print(type(eq)) # <class 'xarray.core.dataset.Dataset'>
# 主要数据变量:
print(list(eq.data_vars))
# ['Phase', 'NP', 'MU', 'X', 'Y', 'GM', ...]
# ============================================
# 1. Phase: 相名
# ============================================
phase_names = eq.Phase.values
# 形状: (n_conditions, n_phases)
# 类型: numpy.ndarray of strings/bytes
# 示例: [['FCC_A1', 'M23C6', '', '']]
# ============================================
# 2. NP: 相的摩尔分数
# ============================================
phase_fractions = eq.NP.values
# 形状: (n_conditions, n_phases)
# 类型: numpy.ndarray of floats
# 示例: [[0.95, 0.05, 0.0, 0.0]]
# 注意:
# - 不稳定相的分数为0或NaN
# - Σ NP = 1 (归一化)
# ============================================
# 3. X: 各相的成分
# ============================================
compositions = eq.X.values
# 形状: (n_conditions, n_phases, n_components)
# 类型: numpy.ndarray of floats
# 示例:
# FCC_A1: [0.92(Fe), 0.02(C), 0.06(Cr)]
# M23C6: [0.70(Fe), 0.25(C), 0.05(Cr)]
# ============================================
# 4. MU: 化学势
# ============================================
chemical_potentials = eq.MU.values
# 形状: (n_conditions, n_components)
# 类型: numpy.ndarray of floats
# 单位: J/mol
# 示例: [-50000(Fe), -30000(C), -45000(Cr)]
# ============================================
# 5. GM: 相的摩尔吉布斯能
# ============================================
gibbs_energies = eq.GM.values
# 形状: (n_conditions, n_phases)
# 类型: numpy.ndarray of floats
# 单位: J/mol
# 示例:
# FCC_A1: -12000 J/mol
# M23C6: -15000 J/mol
实际提取示例
python
def extract_results(eq):
"""
从equilibrium结果中提取有用信息
"""
# ==========================================
# 提取稳定相及其分数
# ==========================================
stable_phases = {}
phase_array = eq.Phase.values.flatten()
np_array = eq.NP.values.flatten()
for phase_name, fraction in zip(phase_array, np_array):
# 过滤条件
if (phase_name and # 非空
phase_name != '' and # 非空字符串
not np.isnan(fraction) and # 非NaN
fraction > 1e-6): # 分数阈值
# 字节转字符串
if isinstance(phase_name, bytes):
phase_name = phase_name.decode('utf-8')
phase_name = phase_name.strip()
# 累加(可能有多个sublattice)
if phase_name in stable_phases:
stable_phases[phase_name] += fraction
else:
stable_phases[phase_name] = fraction
# ==========================================
# 提取各相成分
# ==========================================
phase_compositions = {}
X_array = eq.X.values # (n_cond, n_phases, n_comps)
for i, phase_name in enumerate(phase_array):
if phase_name and phase_name != '' and phase_name in stable_phases:
if isinstance(phase_name, bytes):
phase_name = phase_name.decode('utf-8')
phase_name = phase_name.strip()
# 提取该相的成分
comp = X_array[0, i, :] # 假设单条件
phase_compositions[phase_name] = {
'FE': comp[0],
'C': comp[1],
'CR': comp[2],
# ... 根据实际组分顺序
}
# ==========================================
# 计算总吉布斯能
# ==========================================
GM_array = eq.GM.values
NP_array = eq.NP.values
total_gibbs = np.nansum(GM_array * NP_array)
# ==========================================
# 提取化学势
# ==========================================
MU_array = eq.MU.values # (n_cond, n_comps)
chemical_potentials = {
'FE': MU_array[0, 0],
'C': MU_array[0, 1],
'CR': MU_array[0, 2],
# ...
}
return {
'stable_phases': stable_phases,
'phase_compositions': phase_compositions,
'total_gibbs': total_gibbs,
'chemical_potentials': chemical_potentials
}
# 使用示例
results = extract_results(eq)
print("稳定相:")
for phase, frac in results['stable_phases'].items():
print(f" {phase}: {frac*100:.2f}%")
print("\n各相成分:")
for phase, comp in results['phase_compositions'].items():
print(f" {phase}:")
for el, x in comp.items():
if x > 0.001:
print(f" {el}: {x:.4f}")
print(f"\n总吉布斯能: {results['total_gibbs']:.2f} J/mol")
print("\n化学势:")
for el, mu in results['chemical_potentials'].items():
print(f" μ({el}): {mu:.2f} J/mol")
5. 实际案例分析
5.1 案例:马氏体时效钢相分析
问题背景
合金成分: Fe-0.3C-12Cr-9Ni-3Mo-0.8Al (wt%)
温度条件:
- 奥氏体化: 950-1150°C (5个点)
- 时效: 420, 450, 480, 520°C
目标:
- 计算各温度下的相分数
- 识别析出强化相
- 评估有害相风险
完整代码实现
python
import pandas as pd
import numpy as np
from pycalphad import Database, equilibrium, variables as v
import matplotlib.pyplot as plt
def maraging_steel_analysis():
"""
马氏体时效钢的完整相分析流程
"""
# ==========================================
# 1. 数据准备
# ==========================================
# 加载数据库
db = Database('steel_comprehensive.tdb')
# 定义组分
comps = ['FE', 'C', 'CR', 'NI', 'MO', 'AL', 'VA']
# 定义相(包括所有可能的相)
phases = [
'FCC_A1', # 奥氏体
'BCC_A2', # 铁素体/马氏体
'M23C6', # 碳化物
'M7C3', # 碳化物
'MC_SHP', # MC型碳化物
'CEMENTITE', # Fe3C
'GAMMA_PRIME', # Ni3Al型
'LAVES_PHASE', # Laves相(有害)
'SIGMA', # Sigma相(有害)
'CHI_A12', # Chi相(有害)
'LIQUID' # 液相
]
# 成分(wt%)
composition_wt = {
'C': 0.3,
'CR': 12.0,
'NI': 9.0,
'MO': 3.0,
'AL': 0.8,
'FE': 100 - (0.3 + 12.0 + 9.0 + 3.0 + 0.8) # 74.9
}
print("="*80)
print("马氏体时效钢相分析")
print("="*80)
print(f"\n成分 (wt%):")
for el, wt in composition_wt.items():
print(f" {el:3s}: {wt:6.2f}%")
# ==========================================
# 2. 成分转换
# ==========================================
atomic_weights = {
'C': 12.01, 'CR': 51.996, 'NI': 58.69,
'MO': 95.95, 'AL': 26.98, 'FE': 55.845
}
# 计算摩尔数
moles = {el: wt/atomic_weights[el]
for el, wt in composition_wt.items()}
# 归一化为摩尔分数
total_moles = sum(moles.values())
composition_mole = {el: mol/total_moles
for el, mol in moles.items()}
print(f"\n成分 (at%):")
for el, x in composition_mole.items():
print(f" {el:3s}: {x*100:6.3f}%")
# ==========================================
# 3. 温度系列计算
# ==========================================
# 奥氏体化温度
aus_temps = np.linspace(950, 1150, 5)
# 时效温度
aging_temps = [420, 450, 480, 520]
all_temps = list(aus_temps) + aging_temps
print(f"\n计算温度点:")
print(f" 奥氏体化: {list(aus_temps)}")
print(f" 时效: {aging_temps}")
print(f" 总计: {len(all_temps)} 个温度点")
# ==========================================
# 4. 批量计算
# ==========================================
results = []
print("\n开始计算...")
print("-"*80)
for T in all_temps:
print(f"\n温度: {T:.1f}°C")
# 设置条件(只设置非Fe元素)
conditions = {
v.T: T + 273.15,
v.P: 101325,
v.N: 1.0,
v.X('C'): composition_mole['C'],
v.X('CR'): composition_mole['CR'],
v.X('NI'): composition_mole['NI'],
v.X('MO'): composition_mole['MO'],
v.X('AL'): composition_mole['AL']
# Fe自动确定
}
# 计算
eq = equilibrium(db, comps, phases, conditions, verbose=False)
# 提取相分数
phase_array = eq.Phase.values.flatten()
np_array = eq.NP.values.flatten()
phase_fracs = {phase: 0.0 for phase in phases}
for name, frac in zip(phase_array, np_array):
if name and not np.isnan(frac) and frac > 1e-10:
if isinstance(name, bytes):
name = name.decode('utf-8')
name = name.strip()
if name in phase_fracs:
phase_fracs[name] += frac
# 分类统计
result = {
'Temperature': T,
'Type': 'Austenitizing' if T >= 900 else 'Aging'
}
if T >= 900:
# 奥氏体化温度
result['Austenite'] = phase_fracs['FCC_A1']
result['Ferrite'] = phase_fracs['BCC_A2']
result['M23C6'] = phase_fracs['M23C6']
result['M7C3'] = phase_fracs['M7C3']
result['MC'] = phase_fracs['MC_SHP']
result['Liquid'] = phase_fracs['LIQUID']
print(f" 奥氏体: {result['Austenite']*100:6.2f}%")
print(f" 铁素体: {result['Ferrite']*100:6.2f}%")
if result['M23C6'] > 0.001:
print(f" M23C6: {result['M23C6']*100:6.2f}%")
if result['MC'] > 0.001:
print(f" MC: {result['MC']*100:6.2f}%")
else:
# 时效温度
result['Martensite'] = phase_fracs['BCC_A2']
result['Retained_Austenite'] = phase_fracs['FCC_A1']
# 析出强化相
result['M23C6'] = phase_fracs['M23C6']
result['M7C3'] = phase_fracs['M7C3']
result['MC'] = phase_fracs['MC_SHP']
result['Ni3Al'] = phase_fracs['GAMMA_PRIME']
# 有害相
result['Laves'] = phase_fracs['LAVES_PHASE']
result['Sigma'] = phase_fracs['SIGMA']
result['Chi'] = phase_fracs['CHI_A12']
# 汇总
result['Total_Precipitate'] = (result['M23C6'] + result['M7C3'] +
result['MC'] + result['Ni3Al'])
result['Total_Harmful'] = (result['Laves'] + result['Sigma'] +
result['Chi'])
print(f" 马氏体: {result['Martensite']*100:6.2f}%")
print(f" 残余奥氏体: {result['Retained_Austenite']*100:6.2f}%")
print(f" 析出相总量: {result['Total_Precipitate']*100:6.2f}%")
if result['Total_Harmful'] > 0.001:
print(f" ⚠️ 有害相: {result['Total_Harmful']*100:6.2f}%")
results.append(result)
# ==========================================
# 5. 结果整理和可视化
# ==========================================
df = pd.DataFrame(results)
print("\n" + "="*80)
print("计算结果汇总")
print("="*80)
print(df.to_string(index=False))
# 保存CSV
df.to_csv('maraging_steel_phases.csv', index=False)
print("\n结果已保存: maraging_steel_phases.csv")
# 绘图1: 奥氏体化温度相分布
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
df_aus = df[df['Type'] == 'Austenitizing']
ax = axes[0]
ax.plot(df_aus['Temperature'], df_aus['Austenite']*100,
marker='o', label='Austenite', linewidth=2)
ax.plot(df_aus['Temperature'], df_aus['Ferrite']*100,
marker='s', label='Ferrite', linewidth=2)
ax.plot(df_aus['Temperature'], df_aus['M23C6']*100,
marker='^', label='M23C6', linewidth=2)
ax.set_xlabel('Temperature (°C)', fontsize=12)
ax.set_ylabel('Phase Fraction (%)', fontsize=12)
ax.set_title('Austenitizing Temperature', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(True, alpha=0.3)
# 绘图2: 时效温度相分布
df_aging = df[df['Type'] == 'Aging']
ax = axes[1]
ax.plot(df_aging['Temperature'], df_aging['Martensite']*100,
marker='o', label='Martensite', linewidth=2)
ax.plot(df_aging['Temperature'], df_aging['Retained_Austenite']*100,
marker='s', label='Retained Austenite', linewidth=2)
ax.plot(df_aging['Temperature'], df_aging['Total_Precipitate']*100,
marker='^', label='Total Precipitate', linewidth=2)
ax.plot(df_aging['Temperature'], df_aging['Total_Harmful']*100,
marker='v', label='Total Harmful', linewidth=2, color='red')
ax.set_xlabel('Temperature (°C)', fontsize=12)
ax.set_ylabel('Phase Fraction (%)', fontsize=12)
ax.set_title('Aging Temperature', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('maraging_steel_analysis.png', dpi=300)
print("图表已保存: maraging_steel_analysis.png")
# ==========================================
# 6. 关键发现总结
# ==========================================
print("\n" + "="*80)
print("关键发现")
print("="*80)
# 最佳奥氏体化温度
max_aus_row = df_aus.loc[df_aus['Austenite'].idxmax()]
print(f"\n1. 最佳奥氏体化温度:")
print(f" {max_aus_row['Temperature']:.0f}°C")
print(f" 奥氏体含量: {max_aus_row['Austenite']*100:.2f}%")
print(f" δ铁素体: {max_aus_row['Ferrite']*100:.2f}%")
# 最佳时效温度
max_ppt_row = df_aging.loc[df_aging['Total_Precipitate'].idxmax()]
print(f"\n2. 析出相最多的时效温度:")
print(f" {max_ppt_row['Temperature']:.0f}°C")
print(f" 析出相总量: {max_ppt_row['Total_Precipitate']*100:.2f}%")
# 有害相警告
harmful_data = df_aging[df_aging['Total_Harmful'] > 0.005]
if len(harmful_data) > 0:
print(f"\n3. ⚠️ 有害相警告:")
for _, row in harmful_data.iterrows():
print(f" {row['Temperature']:.0f}°C: {row['Total_Harmful']*100:.2f}%")
else:
print(f"\n3. ✓ 无显著有害相")
# 推荐热处理工艺
print(f"\n4. 推荐热处理工艺:")
print(f" ① 奥氏体化: {max_aus_row['Temperature']:.0f}°C, 1-2小时")
print(f" ② 淬火: 快速冷却至室温")
print(f" ③ 时效: {max_ppt_row['Temperature']:.0f}°C, 3-6小时")
return df
# 运行分析
if __name__ == "__main__":
df_results = maraging_steel_analysis()
6. 常见问题与调试
6.1 常见错误及解决方案
错误1: 成分过约束
python
# ❌ 错误写法
conditions = {
v.T: 1223.15,
v.X('FE'): 0.80, # ← 不要设置!
v.X('C'): 0.15,
v.X('CR'): 0.05
}
# 错误: 4个成分,但只有3个自由度
# ✅ 正确写法
conditions = {
v.T: 1223.15,
v.X('C'): 0.15,
v.X('CR'): 0.05
# Fe自动确定为 1 - 0.15 - 0.05 = 0.80
}
错误2: 单位混淆
python
# ❌ 温度单位错误
conditions = {
v.T: 950, # 错误!950K太低
}
# ✅ 正确:摄氏度转开尔文
conditions = {
v.T: 950 + 273.15, # 1223.15 K
}
# ❌ 压力单位错误
conditions = {
v.P: 1, # 1 Pa太小!
}
# ✅ 正确:标准大气压
conditions = {
v.P: 101325, # Pa
}
错误3: 相名不匹配
python
# 检查数据库中的实际相名
db = Database('steel.tdb')
print(list(db.phases.keys()))
# 输出: ['FCC_A1', 'BCC_A2', 'M23C6', ...]
# ❌ 使用不存在的相名
phases = ['AUSTENITE', 'FERRITE'] # 错误!
# ✅ 使用数据库中的实际名称
phases = ['FCC_A1', 'BCC_A2']
6.2 调试技巧
技巧1: 开启详细输出
python
eq = equilibrium(db, comps, phases, conditions,
verbose=True) # ← 显示详细信息
技巧2: 验证输入
python
def validate_conditions(conditions, comps):
"""验证热力学条件的合理性"""
# 检查温度
T = conditions.get(v.T, 0)
if T < 273.15:
print(f"⚠️ 温度过低: {T} K ({T-273.15:.1f}°C)")
return False
if T > 3000:
print(f"⚠️ 温度过高: {T} K")
return False
# 检查成分总和
total_X = 0.0
for key, val in conditions.items():
if hasattr(key, 'species'):
total_X += val
if total_X >= 1.0:
print(f"⚠️ 成分总和 >= 1: {total_X:.4f}")
print(" 提示: 不要设置基体元素的摩尔分数")
return False
print(f"✓ 条件验证通过")
print(f" 温度: {T-273.15:.1f}°C")
print(f" 成分总和: {total_X:.4f}")
print(f" 基体元素(自动): {1-total_X:.4f}")
return True
# 使用
if validate_conditions(conditions, comps):
eq = equilibrium(db, comps, phases, conditions)
技巧3: 结果完整性检查
python
def check_results(eq):
"""检查计算结果的完整性"""
phase_array = eq.Phase.values.flatten()
np_array = eq.NP.values.flatten()
# 统计稳定相
stable_count = 0
total_fraction = 0.0
for name, frac in zip(phase_array, np_array):
if name and not np.isnan(frac) and frac > 1e-6:
stable_count += 1
total_fraction += frac
print(f"\n结果检查:")
print(f" 稳定相数量: {stable_count}")
print(f" 相分数总和: {total_fraction:.6f}")
if abs(total_fraction - 1.0) > 0.01:
print(f" ⚠️ 警告: 相分数总和偏离1.0!")
return False
if stable_count == 0:
print(f" ⚠️ 警告: 没有稳定相!")
return False
print(f" ✓ 结果正常")
return True
# 使用
eq = equilibrium(db, comps, phases, conditions)
check_results(eq)
6.3 性能优化
优化1: 减少候选相
python
# ❌ 包含所有相(慢)
phases = list(db.phases.keys()) # 可能有50+个相
# ✅ 只包含相关相(快)
phases = ['FCC_A1', 'BCC_A2', 'M23C6', 'M7C3']
# 计算速度提升5-10倍
优化2: 并行计算
python
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
def calc_single(args):
"""单个计算任务"""
T, comp_dict = args
conditions = {v.T: T + 273.15, v.P: 101325, v.N: 1.0}
conditions.update(comp_dict)
eq = equilibrium(db, comps, phases, conditions, verbose=False)
return T, extract_phases(eq)
# 准备任务
tasks = [(T, comp_dict) for T in temperatures]
# 并行执行
num_workers = multiprocessing.cpu_count() - 1
with ProcessPoolExecutor(max_workers=num_workers) as executor:
results = list(executor.map(calc_single, tasks))
优化3: 缓存结果
python
import pickle
def save_results(eq, filename):
"""保存计算结果"""
with open(filename, 'wb') as f:
pickle.dump(eq, f)
def load_results(filename):
"""加载已保存的结果"""
with open(filename, 'rb') as f:
return pickle.load(f)
# 使用
if os.path.exists('results_950C.pkl'):
eq = load_results('results_950C.pkl')
else:
eq = equilibrium(db, comps, phases, conditions)
save_results(eq, 'results_950C.pkl')
7. 总结与扩展
7.1 CALPHAD方法核心要点
1. 理论基础
└─ 吉布斯能最小化原理
└─ 化学势平衡条件
└─ 热力学模型(Redlich-Kister等)
2. 计算流程
└─ 输入:T, P, X_total
└─ 优化:min G_total
└─ 输出:相分数、成分、化学势
3. 关键技巧
└─ 不设置基体元素摩尔分数
└─ 单位转换(K, Pa, mol)
└─ 结果验证(Σ相分数 = 1)
7.2 pycalphad最佳实践
python
# 1. 数据准备
db = Database('your.tdb')
comps = ['FE', 'C', 'CR', 'VA'] # 必含VA
phases = ['FCC_A1', 'BCC_A2'] # 精简相列表
# 2. 条件设置
conditions = {
v.T: T_celsius + 273.15,
v.P: 101325,
v.N: 1.0,
v.X('C'): x_C,
v.X('CR'): x_Cr
# 不设置Fe!
}
# 3. 执行计算
eq = equilibrium(db, comps, phases, conditions)
# 4. 提取结果
phases_dict = extract_stable_phases(eq)
# 5. 验证
assert abs(sum(phases_dict.values()) - 1.0) < 0.01
7.3 进一步学习资源
- 官方文档: https://pycalphad.org
- 示例代码: https://github.com/pycalphad/pycalphad-examples
- 理论书籍: "Computational Thermodynamics: The Calphad Method"
- TDB数据库 :
- TCFE (钢铁)
- TCNI (镍合金)
- TTAL (铝合金)