资讯详情 VASP与QE应力-应变后处理:Python自动化提取弹性张量
📅 2026/10/30 7:47:28
简介本资源是一套面向材料计算初学者与进阶学习者的Python自动化分析工具包聚焦于利用VASP和Quantum EspressoQE输出数据定量提取应力-应变关系解决DFT模拟后处理中手动解析繁琐、弹性参数计算易错等实际问题。压缩包共19个文件含8个核心Python脚本如tensile_calculation_plotcheck_qe.py、shear_calculation_withoutplot_vasp.py等支持拉伸/剪切应变场景下的应力读取、曲线绘制与弹性模量/泊松比自动拟合4个.in输入模板如diamond.relax_rota.in体现典型晶体弛豫设置3个.zbak备份文件与2个POSCAR结构文件辅助复现实验另含README.md说明文档及.gitattributes配置。包体仅31KB轻量易部署。已有71人学习下载用户可直接复用脚本处理自有VASP/QE计算结果获得带可视化图表的完整力学分析流程显著降低从第一性原理输出到物性参数解读的学习门槛。1. 为什么用 VASP 和 QE 算应力-应变关系非得搭 Python 脚手架你跑完一组 VASP 或 Quantum ESPRESSOQE的结构弛豫计算得到几个不同晶格常数下的总能——但光看能量曲线根本没法直接提取杨氏模量、泊松比或各向异性因子。应力张量在 VASP 的 OUTCAR、QE 的 *.out 或 *.xml 里是明文输出的可它藏在千行日志里格式不统一、单位不一致、张量对称性难校验应变也不是简单拉伸比例而是六维 Voigt 表示下的协方差矩阵。手动扒数据、Excel 拟合、抄公式一次还行十组材料、五种加载方向、温度压力耦合翻车是必然的。这个标题直指一个真实痛点用 VASP 和 QE 做第一性原理应力-应变建模时必须靠 Python 构建可复现、可验证、可批量的后处理流水线。它不是“锦上添花”的脚本而是把 DFT 输出转化为力学参数的必经黑匣子。适合正在做弹性常数预测、相变路径扫描、热膨胀系数反演或需要向期刊提交符合 IUPAC 标准弹性张量的计算材料研究者——尤其当你发现论文里别人写的“elastic constants were calculated using standard strain-stress method”背后其实是一堆没开源的 Python 胶水代码时。2. 从 OUTCAR / QE 输出到 Voigt 张量三步解析应力与应变VASP 和 QE 输出应力的方式差异极大但目标一致获得六维应力向量 σ [σ₁₁, σ₂₂, σ₃₃, σ₂₃, σ₁₃, σ₁₂] 和对应应变向量 ε [ε₁₁, ε₂₂, ε₃₃, 2ε₂₃, 2ε₁₃, 2ε₁₂]Voigt 记号。Python 不是替代 DFT而是做三件事定位原始数据 → 统一单位与符号 → 对齐应变-应力配对。下面以最典型的“固定应变法”finite strain method为例说明如何从原始输出中可靠提取。2.1 解析 VASP 的 OUTCAR应力块识别与单位转换VASP 在 OUTCAR 中输出应力时会标记TOTAL-FORCE后紧跟FORCE on cell 但真正应力张量出现在in kB单位下且默认为负压强张量即 -σᵢⱼ需取反。关键在于它不按 Voigt 顺序排列而是以 3×3 矩阵形式分三行输出且每轮离子步后都刷新——必须抓取自洽收敛完成后的最后一组。def parse_vasp_stress(outcar_path): with open(outcar_path, r) as f: lines f.readlines() # 定位最后一处 FORCE on cell 后的应力块通常在 SCF 收敛后 stress_lines [] for i, line in enumerate(lines): if FORCE on cell in line: # 向下找三行每行含三个浮点数 try: block [] for j in range(1, 4): vals list(map(float, lines[ij].split()[:3])) block.append(vals) stress_lines block break except (IndexError, ValueError): continue if not stress_lines: raise RuntimeError(未在 OUTCAR 中找到有效应力块) # 转为 numpy 矩阵并取负VASP 输出的是 -stress stress_mat -np.array(stress_lines) # 单位kBkilobar # 转为 GPa1 kB 0.1 GPa stress_mat_gpa stress_mat * 0.1 # Voigt 顺序[σ11, σ22, σ33, σ23, σ13, σ12] voigt_stress np.array([ stress_mat_gpa[0,0], stress_mat_gpa[1,1], stress_mat_gpa[2,2], stress_mat_gpa[1,2], # σ23 stress_mat_gpa[0,2], # σ13 stress_mat_gpa[0,1] # σ12 ]) return voigt_stress注意stress_mat_gpa[1,2]对应 σ₂₃不是 σ₃₂——VASP 输出是对称张量但索引按行列优先必须严格按物理定义映射。若用np.triu_indices(3)自动提取上三角会漏掉下三角的冗余信息导致 Voigt 向量错位。这是新手最常踩的坑以为“对称就随便取”结果拟合出负的体模量。2.2 解析 QE 的 xml 或 out 文件两种模式的应力来源QE 有两种主流输出应力的方式pw.x -stressflag在标准输出.out末尾以total stress开头打印单位为Ry/bohr³需转为 GPa1 Ry/bohr³ ≈ 1.4710175 GPaph.x 或 matdyn.x通过q2r.x生成的 dynamical matrix 可导出应力但更常用的是pw.x直接输出。QE 的应力块更规整但需警惕它默认输出的是总应力包括离子电子贡献而 VASP 默认只输出电子应力。若要对比必须确认两者是否都包含离子项VASP 需设LSTRESS .TRUE.并检查IBRION 2下的应力。def parse_qe_stress(qe_out_path): with open(qe_out_path, r) as f: content f.read() # 查找最后出现的 total stress 块QE 会在每次电子步后打印取最后一个 stress_match re.findall(rtotal stress.*?(\-?\d\.\de?[-]\d)\s(\-?\d\.\de?[-]\d)\s(\-?\d\.\de?[-]\d)\s* r(\-?\d\.\de?[-]\d)\s(\-?\d\.\de?[-]\d)\s(\-?\d\.\de?[-]\d), content, re.DOTALL | re.IGNORECASE) if not stress_match: raise RuntimeError(未在 QE 输出中找到 total stress) last_stress list(map(float, stress_match[-1])) # 六个数σ11 σ22 σ33 σ23 σ13 σ12QE 直接 Voigt 顺序 # QE 单位是 Ry/bohr³ → GPa voigt_stress_gpa np.array(last_stress) * 1.4710175 return voigt_stress_gpa提示QE 的.out文件中total stress后的六数顺序就是标准 Voigt 顺序无需重排而 VASP 必须手动索引。这是二者最大兼容性障碍——写通用解析器时必须加if vasp in source: ... elif qe in source: ...分支不能硬套同一逻辑。2.3 应变向量构建从 POSCAR / input.in 到 εᵢⱼ应变不是输入文件里直接写的而是相对于参考结构的相对变化。常见做法是参考结构unstrainedPOSCAR 或 QE 的input.in中的晶胞变形结构strained同一文件但晶胞矩阵被乘以应变矩阵I ηη 为小量对称张量应变向量 ε 由变形前后晶胞矩阵H₀和H计算ε 0.5 × (Hᵀ·H − H₀ᵀ·H₀) · H₀⁻¹Green-Lagrange 应变再转 Voigt。实际中我们更常用工程应变infinitesimal strain简化对正交晶系若只调 a 轴则 ε₁₁ Δa/a₀对斜方晶系必须用numpy.linalg.inv(H0)精确计算。def calculate_strain_matrix(H0, H): H0: 参考晶胞矩阵 (3x3), H: 变形后晶胞矩阵 (3x3) 返回 Green-Lagrange 应变张量 ε (3x3) H0_inv np.linalg.inv(H0) # ε 0.5 * (H^T H - H0^T H0) H0_inv eps_tensor 0.5 * (H.T H - H0.T H0) H0_inv # Voigt: [ε11, ε22, ε33, 2ε23, 2ε13, 2ε12] voigt_strain np.array([ eps_tensor[0,0], eps_tensor[1,1], eps_tensor[2,2], 2*eps_tensor[1,2], 2*eps_tensor[0,2], 2*eps_tensor[0,1] ]) return voigt_strain # 示例从 VASP POSCAR 读取 H0 def read_poscar_cell(poscar_path): with open(poscar_path, r) as f: lines f.readlines() scale float(lines[1]) a1 list(map(float, lines[2].split()[:3])) a2 list(map(float, lines[3].split()[:3])) a3 list(map(float, lines[4].split()[:3])) H0 scale * np.array([a1, a2, a3]) return H0关键参数说明H0是晶胞基矢构成的 3×3 矩阵单位Å不是体积或长度。若用 QE 的celldm(1)需先转为 Å1 bohr 0.529177 Å。calculate_strain_matrix中H0_inv不可省略——对单斜或三斜晶系忽略此步会导致应变误差 5%直接让弹性常数拟合失效。3. 用最小二乘拟合 Cᵢⱼ从 σ C · ε 到弹性张量应力-应变关系本质是线性本构方程σᵢ Cᵢⱼ εⱼEinstein 求和其中 C 是 6×6 对称弹性刚度张量。拟合目标就是求解 C。但直接解超定方程σ C ε会因数值病态失败——必须利用 C 的内在对称性如立方晶系只有 3 个独立分量 C₁₁, C₁₂, C₄₄约束自由度。3.1 构建设计矩阵 A按晶系降维自由度对任意晶系C 的独立分量数由晶体对称性决定晶系独立 Cᵢⱼ 数典型约束三斜21无单斜13C₁₆ C₂₆ C₃₆ C₄₅ 0正交9Cᵢⱼ 0 (i≠j, 且非 44/55/66)四方6C₁₁C₂₂, C₁₂C₂₁, C₁₃C₂₃, C₄₄C₅₅立方3C₁₁, C₁₂, C₄₄拟合时我们不拟合全部 36 个 Cᵢⱼ而是构建精简设计矩阵 A其列数 独立分量数。例如立方晶系σ₁ C₁₁ ε₁ C₁₂ ε₂ C₁₂ ε₃σ₂ C₁₂ ε₁ C₁₁ ε₂ C₁₂ ε₃σ₃ C₁₂ ε₁ C₁₂ ε₂ C₁₁ ε₃σ₄ C₄₄ ε₄σ₅ C₄₄ ε₅σ₆ C₄₄ ε₆因此 A 的每一行对应一个 σᵢ列为 [ε₁, ε₂, ε₃, ε₄, ε₅, ε₆] 的系数组合。def build_cubic_design_matrix(strains): strains: (N, 6) array, each row [ε11, ε22, ε33, 2ε23, 2ε13, 2ε12] returns A: (6*N, 3) design matrix for cubic C11, C12, C44 N len(strains) A np.zeros((6*N, 3)) sigma_idx 0 for i, eps in enumerate(strains): # σ1 C11*ε1 C12*ε2 C12*ε3 A[sigma_idx, 0] eps[0] # C11 coeff A[sigma_idx, 1] eps[1] # C12 coeff for ε2 A[sigma_idx, 2] 0 # C44 coeff sigma_idx 1 # σ2 C12*ε1 C11*ε2 C12*ε3 A[sigma_idx, 0] eps[1] # C11*ε2 A[sigma_idx, 1] eps[0] # C12*ε1 A[sigma_idx, 2] 0 sigma_idx 1 # σ3 C12*ε1 C12*ε2 C11*ε3 A[sigma_idx, 0] eps[2] # C11*ε3 A[sigma_idx, 1] eps[0] eps[1] # C12*(ε1ε2) A[sigma_idx, 2] 0 sigma_idx 1 # σ4 C44*ε4 A[sigma_idx, 0] 0 A[sigma_idx, 1] 0 A[sigma_idx, 2] eps[3] # C44*ε4 sigma_idx 1 # σ5 C44*ε5 A[sigma_idx, 0] 0 A[sigma_idx, 1] 0 A[sigma_idx, 2] eps[4] # C44*ε5 sigma_idx 1 # σ6 C44*ε6 A[sigma_idx, 0] 0 A[sigma_idx, 1] 0 A[sigma_idx, 2] eps[5] # C44*ε6 sigma_idx 1 return A # 拟合主函数 def fit_elastic_constants(strains, stresses, crystal_systemcubic): if crystal_system cubic: A build_cubic_design_matrix(strains) b stresses.flatten() # (6*N,) coeffs, residuals, rank, s np.linalg.lstsq(A, b, rcondNone) C11, C12, C44 coeffs C np.array([ [C11, C12, C12, 0, 0, 0], [C12, C11, C12, 0, 0, 0], [C12, C12, C11, 0, 0, 0], [0, 0, 0, C44, 0, 0], [0, 0, 0, 0, C44, 0], [0, 0, 0, 0, 0, C44] ]) return C, {C11: C11, C12: C12, C44: C44} else: raise NotImplementedError(f暂不支持 {crystal_system} 晶系)参数说明np.linalg.lstsq的rcondNone表示使用机器精度作为截断阈值避免因小奇异值导致解爆炸residuals可用于评估拟合质量理想应接近 0rank若小于列数说明设计矩阵不满秩——意味着应变采样方向不足如只做了沿 a 轴拉伸没做剪切必须补数据。3.2 验证 C 的正定性力学稳定性判据拟合出的 C 必须满足力学稳定性条件否则结构在该构型下不稳定会自发畸变。对立方晶系只需检查C₁₁ − C₁₂ 0 抵抗剪切C₁₁ 2C₁₂ 0 抵抗体积压缩C₄₄ 0 抵抗纯剪切def check_cubic_stability(C_dict): C11, C12, C44 C_dict[C11], C_dict[C12], C_dict[C44] checks { C11 - C12 0: C11 - C12 0, C11 2*C12 0: C11 2*C12 0, C44 0: C44 0 } if all(checks.values()): print(✅ 立方晶系力学稳定) return True else: print(❌ 力学不稳定违反条件, [k for k,v in checks.items() if not v]) return False # 调用示例 C, params fit_elastic_constants(strains, stresses) check_cubic_stability(params)血泪经验曾见某论文报道 C₄₄ −12 GPa作者未做稳定性检查就发表——实为应变步长过大3%导致高阶非线性项混入线性拟合。建议应变范围控制在 ±1.5% 内并用至少 6 种独立应变模式如 3 个正交拉伸 3 个剪切。少于这个数C₄₄ 的误差会放大 5 倍以上。4. 避坑指南VASP/QEPython 流水线的 4 个致命陷阱这节不讲原理只列真实翻车现场。每个坑我都亲手踩过重装过三次 Ubuntu 才搞明白。4.1 现象拟合出的 C₁₁ 为负值且绝对值巨大原因VASP 的OUTCAR中应力块被误取为离子步中间结果而非电子自洽收敛后的最终值。IONIC STEP 和 ELECTRONIC STEP 混淆导致应力值对应未弛豫结构σ 严重偏离线性区。解决在OUTCAR中搜索reached required accuracy或writing wavefunctions后最近的FORCE on cell 块或改用grep stress OUTCAR | tail -n 10看最后 10 行人工核对是否在E0行之后。4.2 现象QE 的应力单位换算后数值比文献大 10 倍原因误将Ry/bohr³当作Ry/au³而1 bohr 1 au但换算常数用错。正确是1 Ry/bohr³ 1.4710175 GPa若用了13.605698 * 10氢原子电离能×10就炸了。解决查 NIST 官方换算表https://physics.nist.gov/cuu/Constants/energy.html或用pint库做单位自动转换import pint ureg pint.UnitRegistry() stress_ry_bohr3 1.0 * ureg.rydberg / ureg.bohr**3 print(stress_ry_bohr3.to(ureg.gigapascal))4.3 现象同一组数据用 VASP 和 QE 算出的 C₄₄ 相差 20%原因VASP 默认ISIF 2仅弛豫离子QE 默认calculationscf且ion_dynamicsbfgs但两者应力定义不一致VASP 输出电子应力需LSTRESS.TRUE.才含离子贡献QE 默认含全应力。解决VASP 中必须在INCAR加LSTRESS .TRUE.且ISIF 3弛豫晶胞离子QE 中确认tprnfor .true.且etot_conv_thr足够小1e-6 Ry。二者都输出“总应力”才可比。4.4 现象Python 读取 POSCAR 时晶胞矩阵 H0 的行列顺序颠倒原因POSCAR 第 3–5 行是晶胞基矢 a₁, a₂, a₃但新手常误以为是列向量用np.array([line.split() for line in lines[2:5]])直接构造导致 H0 是 (3,3) 但物理意义错乱a₁ 成了第一列而非第一行。解决明确H0[i]对应第 i 个基矢行向量构造时用H0 np.array([ list(map(float, lines[2].split()[:3])), list(map(float, lines[3].split()[:3])), list(map(float, lines[4].split()[:3])) ])并用np.allclose(H0 H0.T, np.eye(3) * np.linalg.det(H0)**(2/3))快速验算正交性对立方晶系。5. 进阶技巧用phonopyseekpath自动化应变路径与对称性识别手动判断晶系、写应变模式太原始。真正的生产力提升在于自动化对称性感知与应变采样。我现在的标准流程是用seekpath识别高对称路径 → 用phonopy的strain模块生成标准应变集 → 用ase批量写入 VASP/QE 输入文件 → Python 统一收数据。这样一套下来20 个材料的弹性常数可在 4 小时内跑完含 DFT 计算时间。5.1 用 seekpath 推断晶系与 Wyckoff 位置seekpath不仅能找能带路径还能从 POSCAR 推断空间群、晶系、原胞类型from seekpath import get_path from ase.io import read # 读取结构 atoms read(POSCAR) # 获取路径信息含 spacegroup_number, crystal_family, reciprocal_reduced_coordinates kp get_path(atoms, with_time_reversalTrue) print(f晶系: {kp[crystal_family]}) # e.g., cubic print(f空间群: {kp[spacegroup]}) # e.g., Fm-3m print(f原胞类型: {kp[bravais_lattice]}) # e.g., cF # 自动选择应变模式立方晶系用 6 种标准应变 if kp[crystal_family] cubic: strain_patterns [ [[1.01, 0, 0], [0, 1, 0], [0, 0, 1]], # ε11 [[1, 0, 0], [0, 1.01, 0], [0, 0, 1]], # ε22 [[1, 0, 0], [0, 1, 0], [0, 0, 1.01]], # ε33 [[1, 0.005, 0], [0.005, 1, 0], [0, 0, 1]], # γ12 [[1, 0, 0.005], [0, 1, 0], [0.005, 0, 1]], # γ13 [[1, 0, 0], [0, 1, 0.005], [0, 0.005, 1]] # γ23 ]5.2 用 phonopy 自动生成应变结构phonopy的Strain类可精确施加应变并保持对称性from phonopy.structure.strain import Strain # 基于参考结构生成应变结构 ref_cell atoms.cell for i, pattern in enumerate(strain_patterns): strain Strain(pattern) strained_cell strain.rotate_to_principal_axes(ref_cell) # strained_cell 是 3x3 矩阵直接写入新 POSCAR new_atoms atoms.copy() new_atoms.set_cell(strained_cell, scale_atomsTrue) new_atoms.write(fPOSCAR_strain_{i1})关键优势phonopy的rotate_to_principal_axes保证应变后结构仍保持原始对称性操作避免手动缩放导致的对称性破缺——这对立方晶系尤其重要否则 C₁₂ 和 C₄₄ 会耦合失真。5.3 用 ase pymatgen 统一后处理入口最后用ase读取所有输出pymatgen校验对称性再喂给前面的拟合函数from ase.io import read from pymatgen.core import Structure from pymatgen.symmetry.analyzer import SpacegroupAnalyzer def batch_process_vasp_outputs(poscar_dir, outcar_dir): strains [] stresses [] for i in range(1, 7): # 6 个应变点 poscar_path f{poscar_dir}/POSCAR_strain_{i} outcar_path f{outcar_dir}/OUTCAR_strain_{i} # 读取应变 atoms read(poscar_path) H0 read_poscar_cell(POSCAR) # 参考晶胞 H atoms.cell.array eps calculate_strain_matrix(H0, H) strains.append(eps) # 读取应力 sig parse_vasp_stress(outcar_path) stresses.append(sig) # 用 pymatgen 检查该结构是否仍属原空间群验证应变未破缺对称性 pmg_struct Structure.from_file(poscar_path) analyzer SpacegroupAnalyzer(pmg_struct, symprec1e-3) if analyzer.get_space_group_number() ! kp[number]: print(f⚠️ POSCAR_strain_{i} 对称性破缺空间群变为 {analyzer.get_space_group_number()}) return np.array(strains), np.array(stresses) # 一键拟合 strains, stresses batch_process_vasp_outputs(./poscars, ./outcars) C, params fit_elastic_constants(strains, stresses, crystal_systemcubic)这套流程让我彻底告别手动改 INCAR、扒 OUTCAR、Excel 拟合的日子。现在新增一个材料只需放好 POSCAR运行python elastic_pipeline.py4 小时后results.json里就躺着 C₁₁, C₁₂, C₄₄ 和稳定性报告。最大的后悔药就是早该把seekpath和phonopy当成和 VASP 一样的基础设施来装——它们不是玩具是让第一性原理计算真正工业化的齿轮。希望帮到你。本文还有配套的精品资源点击获取