Skip to content

批量计算二面角/键角/键长代码的使用

1. 代码简介

代码定义了一个Atom类,在类的实例化时传入原子编号。调用cal()函数可以根据原子编号的数量,批量计算二面角/键角/键长。cal()默认计算脚本目录下所有的 Gaussian 输出文件(.log),如果需要计算特定的文件,请在cal()传入文件名。

2. 使用示例

将 .py 文件和存有原子数据的 .log 文本放在同一目录下。

2.1 二面角的计算

​ 在类的实例化时,传入四个参数。注意中间两个原子为面的交线。

import math
import os
import numpy as np

a = Atom(9, 8, 13, 14)  # 实例化
a1 = a.cal()
""" >>>
文件10.log中,(9, 8, 13, 14)号原子形成的二面角是:180.00
文件11.log中,(9, 8, 13, 14)号原子形成的二面角是:170.00
文件14.log中,(9, 8, 13, 14)号原子形成的二面角是:140.00
文件16.log中,(9, 8, 13, 14)号原子形成的二面角是:120.00
文件19.log中,(9, 8, 13, 14)号原子形成的二面角是:100.00
"""

计算特定某几个文件:

a = Atom(9, 8, 13, 14)  # 实例化
a1 = a.cal('10.log','11.log')
""" >>>
文件10.log中,(9, 8, 13, 14)号原子形成的二面角是:180.00
文件11.log中,(9, 8, 13, 14)号原子形成的二面角是:170.00
"""

控制保留小数位数,使用 decimal 参数(默认保留两位小数):

a = Atom(9, 8, 13, 14)  # 实例化
a1 = a.cal('10.log', decimal=5)
""" >>>
文件10.log中,(9, 8, 13, 14)号原子形成的二面角是:179.99520
"""

2.2 键角的计算

只需要在类的实例化时传入三个参数即可,注意第二个原子为顶角

a = Atom(9, 8, 13)  # 实例化
a1 = a.cal('10.log', decimal=5)
""" >>>
文件10.log中,(9, 8, 13)号原子形成的键角是:27.06505
"""

2.3 键长的计算

同理,在类的实例化时传入两个参数。

3. 可能出现的问题

1 结果得出的二面角与我想要的二面角互补?

注意实例化时四个原子编号的顺序。对于(1,2,3,4)中,求的是123平面和234平面的二面角。

2 如何把结果导入到 csv 或其他文本中?

还未完成,自己写一个脚本去吧。

4. 源码

#!/usr/bin/python3
import math
import os
import numpy as np


class Atom:
    """可以用来批量计算.log文件中的键长、键角和二面角"""
    def __init__(self, *atom):
        self.matrix = None
        self.atom = atom

    def get_coor_data(self, file_name):
        """从.log文件中获取原子坐标"""
        with open(file_name) as f:
            lines = f.readlines()
            for i, line in enumerate(lines):
                if line.startswith(' NAtoms='):
                    natoms = int(line.split()[1])  # 定位
                elif 'Standard orientation' in line:
                    geom = [row.split()[-3:] for row in lines[i + 5:i + 5 + natoms]]  # 以空格为区分,按列切
                    geom = [[float(item) for item in sublist] for sublist in geom]
        return geom

    def cal(self, *file_name, decimal=2):
        """接收参数,把计算结果输出到屏幕上。"""
        dic = dict(zip([2, 3, 4], ['键长', '键角', '二面角']))  # 建立对应关系
        if not len(self.atom) in [2, 3, 4]:
            print('原子数不对哦')
            exit(0)
        for file in file_name if len(file_name) != 0 else os.listdir():
            if file.endswith('.log'):
                print(f'文件{file}中,{self.atom}号原子形成的{dic[len(self.atom)]}是:', end='')
                print(f'%.{decimal}f' % self.calculate(file))

    def calculate(self, file_name):
        """根据原子坐标数,判断计算键长、键角或二面角"""
        self.matrix = self.get_coor_data(f'{file_name}')
        theta = (self.calculate_bond_length() if len(self.atom) == 2 else
                 self.calculate_bond_angle() if len(self.atom) == 3 else
                 self.calculate_dihedral_angle() if len(self.atom) == 4 else
                 float('inf'))
        return theta

    def calculate_bond_length(self):
        """计算两点间的长度"""
        p1, p2 = [self.matrix[i - 1] for i in self.atom]
        v1 = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
        return np.linalg.norm(v1)

    def calculate_bond_angle(self) -> float:
        """计算三个点形成的角,p2是顶点"""
        p1, p2, p3 = [self.matrix[i - 1] for i in self.atom]
        v1 = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
        v2 = (p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2])
        dot_product = np.dot(v1, v2)
        norm_n1 = np.linalg.norm(v1)
        norm_n2 = np.linalg.norm(v2)
        theta = np.arccos(dot_product / (norm_n1 * norm_n2))
        return math.degrees(theta)

    def calculate_dihedral_angle(self) -> float:
        """计算四个点的二面角,返回角度,p2p3是两面的交线"""
        p1, p2, p3, p4 = [self.matrix[i - 1] for i in self.atom]

        def calculate_norm_vectors(p1, p2, p3):
            """计算三个点所成平面的法向量"""
            v1 = (p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
            v2 = (p3[0] - p1[0], p3[1] - p1[1], p3[2] - p1[2])
            v = np.cross(np.array(v1), np.array(v2))
            return v

        n1 = calculate_norm_vectors(p1, p2, p3)
        n2 = calculate_norm_vectors(p2, p3, p4)
        dot_product = np.dot(n1, n2)
        norm_n1 = np.linalg.norm(n1)
        norm_n2 = np.linalg.norm(n2)
        theta = np.arccos(dot_product / (norm_n1 * norm_n2))
        return math.degrees(theta)
# 使用示例
if __name__ == '__main__':
    a = Atom(9, 8, 13, 14)
    a1 = a.cal()

Comments