https://github.com/xieguigang/mzkit

SMILES字符串是一种在计算化学领域内使用线性ASCII字符串描述一个具有空间立体结构的分子结构所使用的一种语言规范。因为在工作中会需要使用到SMILES字符串做一些分子结构相关的数据建模分析,所以编写了一个很方便的用于SMILES字符串解析操作的模块,在这篇文章中为大家讲解具体的工作原理。

SMILES字符串解析的基础化学知识

我们在弄明白SMILES字符串是怎样阅读以及解析之前,我们首先来复习一些基础的化学知识。这些知识在后面的代码阅读上会用得上。

  1. SMILES字符串主要是以碳原子为组成主体的,一个碳原子,最多可以连接4个原子。一个氧原子,最多可以连接两个原子;而一个氮原子,则可以最多连接三个原子。C,N和O构成了常见的化学物质的原子组成。
  2. SMILES字符串之中并不存储三维结构的具体坐标信息,但是我们可以通过碳原子的化学键之间的夹角结合从SMILES字符串解析出来的分子图计算出空间坐标信息。一个碳原子键角的大小与成键中心原子的杂化状态有关。如,sp3、sp2、sp杂化状态的碳,成键时形成的键角约是109°28'、120°、180°。此外,键角的大小还与中心碳原子上所连的基团有关。

SMILES语言解析分词

在SMILES字符串之中,一般是有原子符号,化学键,以及标记支链所需要的括号这些符号构成,所以我们可以定义SMILES语言的一些基础元素符号可以定义为:

Public Enum ElementTypes
    Element
    Key
    Open
    Close
End Enum

其中:

  • Element 为原子元素符号类型
  • Key 为原子符号之间的化学键类型,默认原子符号之间是以单键连接的
  • Open 即标记出分子支链的起始部位的符号
  • Close 则是标记处分子支链的结束部分的符号

SMILES之中的化学键符号

对于上面提到的SMILES语言之中的Key类型,在SMILES语言之中,定义了4种化学键符号:

符号 化学键
- 单键
= 双键
# 三键
: 四键

对应的VisualBasic的源代码(SMILES/Bonds.vb)为:

''' <summary>
''' Single, double, triple, and aromatic bonds are 
''' represented by the symbols ``-``, ``=``, ``#``, and ``:``,
''' respectively. Adjacent atoms are assumed to be 
''' connected to each other by a single or aromatic 
''' bond (single and aromatic bonds may always be 
''' omitted).
''' </summary>
Public Enum Bonds As Byte
    <Description("-")> [single] = 1
    <Description("=")> [double] = 2
    <Description("#")> triple = 3
    <Description(":")> aromatic = 4
End Enum

在具有了上面的基本元素定义之后,我们就可以开始编写Scanner模块进行SMILES字符串扫描,按照符号规则进行分词处理得到对应的Token列表就好了。对于的解析例子,大家可以阅读Scanner模块之中的walkChar函数的源代码。

总之,对于SMILES字符串的分词操作,大家只需要遵循遇到支链栈符号或者化学键符号就将缓存中的字符串抛出去,遇到字母就写入缓存;对于两个大写字母则会抛出第一个大写字母再将后一个大写字母写入缓存。依照这样子的规则,我们就可以很准确的对SMILES字符串进行分词操作,得到正确的单词列表用于后续的分子图的建立了

在我们进行分词操作的时候,除了分词规则之外,还有一个用于正确的判断出单词类型的函数会比较重要:

Private Function MeasureElement(str As String) As Token
    If str.Length >= 3 AndAlso (str.First = "["c AndAlso str.Last = "]"c) Then
        ' [H]
        str = str.GetStackValue("[", "]")
    End If
    If str.IsPattern("[A-Za-z]+\d+") Then
        ' removes number
        str = str.Match("[a-zA-Z]+")
    End If

    Select Case str
        Case "B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I", "Au", "H"
            Return New Token(ElementTypes.Element, str)
        Case "("
            Return New Token(ElementTypes.Open, str)
        Case ")"
            Return New Token(ElementTypes.Close, str)
        Case Else
            Throw New NotImplementedException(str)
    End Select
End Function

建立分子图

在这里,我们基于一个网络图对象来基于SMILES分词的结果基础上建立对应的分子图模型。在mzkit程序包中的SMILES解析器模块中,分别定义了构建出一个分子图对象所需要的图对象,节点对象,键连接对象,例如:

我们在聊完了建立分子图模型所需要的三个必须的模型对象之后,下面在来看看要怎样解析才可以正确的建立起分子图吧。大家可以结合着mzkit程序包中的SMILES/ParseChain.vb程序源代码来理解这里的构建方法。在构建分子图的过程中,我们实际上在做的一件事就是不断的将元素Atom对象通过化学键连接在一起。所以依照SMILES语言的特点,我们可以指定下面的规则用于连接Atom元素对象:

  • 在SMILES语言之中,大写字符就是代表一个Atom元素,两个相邻的Atom之间,没有显式的使用对应的化学键符号标记出来的话,默认就是单键连接
  • 圆括号内的部分表示一个支链,支链的起始与括号左边的Atom相连

所以,我们可以依照上面的规则,写出下面的分子图的建立方法的过程代码

Private Sub WalkElement(t As Token)
    Dim element As New ChemicalElement(t.text)

    element.ID = graph.vertex.Count
    graph.AddVertex(element)

    If chainStack.Count > 0 Then
        Dim lastElement As ChemicalElement = chainStack.Peek
        Dim bondType As Bonds = If(lastKey Is Nothing, Bonds.single, lastKey.Value)
        ' single bond
        Dim bond As New ChemicalKey With {
            .U = lastElement,
            .V = element,
            .weight = 1,
            .bond = bondType
        }

        Call graph.AddBond(bond)
    End If
    If stackSize.Count > 0 Then
        Call stackSize.Peek.Hit()
    End If

    lastKey = Nothing
    chainStack.Push(element)
End Sub

下面我们来做阅读理解,来具体的说明上面的这个函数是怎么工作的吧。因为在SMILES字符串之中存在分子支链的栈信息,所以我们在这个模块里定义了一个支链的栈对象chainStack。在栈之中,元素类型为Atom,因为支链连接的就是一个Atom元素。对于WalkElement函数,代码从上往下运行:

  • 首先会根据当前的单词文本创建一个Atom元素对象,然后将这个Atom添加进入分子图的节点列表之中,等待创建对应的边链接
  • 我们为了方便统一代码,我们将支链对象和Atom对象都看作为Atom类型,所以在建图的时候会不断的向支链栈中添加Atom元素
  • 如果支链的栈长度大于零,那么我们就可以将当前的Atom对象和栈Peek出来的前一个对象进行化学键连接了。我们只需要在分子图中创建一个边连接对象即可完成化学键连接操作
  • 根据化学键Token的文本字符串的不同,我们可以对 ChemicalKey 对象设置不同的化学键类型

好了,经过上面的过程,我们就已经可以成功的建立起一个分子图对象了。之后我们只需要根据化学键类型,网对应的Atom元素填充氢原子,就可以从SMILES字符串中还原出正确的化学式之类的信息了。

R#脚本解析SMILES字符串

mzkit对R#脚本编程开放了几个用于SMILES解析操作的函数,在这里为大家讲解一下这些函数的用法:

  • parseSMILES 函数用于将线性的SMILES字符串基于前面的方法解析为分子图对象
  • as.formula 函数则是在分子图对象的基础上,将其转换为对应的化学式对象

下面我们来看一些具体R#编程的例子

imports "formula" from "mzkit";

let echo as function(SMILES, prompt) {
    print(prompt);

    SMILES = formula::parseSMILES(SMILES);

    print(toString(as.formula(SMILES)));
    print(`exact mass: ${eval(as.formula(SMILES))}`);
    print("---------------------------------------");

    cat("\n");
}

echo("CC",            "ethane CH3CH3");
echo("C=O",           "formaldehyde (CH2O)");
echo("C=C",           "ethene (CH2=CH2)");
echo("O=C=O",         "carbon dioxide (CO2)");
echo("COC",           "dimethyl ether (CH3OCH3)");
echo("C#N",           "hydrogen cyanide (HCN)");
echo("CCO",           "ethanol (CH3CH2OH)");
echo("[H][H]",        "molecular hydrogen (H2)");
echo("C=C-C-C=C-C-O", "6-hydroxy-1,4-hexadiene CH2=CH-CH2-CH=CH-CH2-OH");
echo("CCN(CC)CC",     "Triethylamine C6H15N");
echo("CC(C)C(=O)O",   "Isobutyric acid C4H8O2");

我稍微数了一下输出的化学式结果与原始的化学式的结果的比较,Atom元素的数量都可以一一对上,说明我们的将SMILES字符串解析建立分子图的过程算法是没有啥太大的问题的。

在从SMILES线性字符串解析出分子图之后,我们可以基于分子图得到的分子化学式做一些分析。在Mzkit的R#程序包中,提供了一些基于化学式进行符号计算的语言特性函数,大家可以阅读《化学式符号运算》了解详细的情况。

Latest posts by xie guigang (see all)

Attachments

2 Responses

Leave a Reply

Your email address will not be published.