根据工作的需要,我为R#脚本解释器添加了一个符号计算的功能,这个符号语言特性在进行一些化学信息学分析的时候会非常有用。例如,我们在分析一些天然产物的质谱数据的时候,会需要通过母离子减掉一些糖来进行中性丢失的计算,基于中性丢失计算来进行一些解谱分析操作。在这个过程之中,化学式符号计算就可以派上很大用场了。假设我们有一个天然产物Cyanidin 3-glucoside-5-(6-p-coumaroylglucoside),从名称我们就可以看出这个天然产物是由一个Cyanidin母核,加上两个葡萄糖以及一个coumaroyl基团构成。这个天然产物的分子化学式为C36H37O18,那现在我们将这个化学式输入到R#解释器之中,按下回车就可以很清楚的了解到这个化学式的元素构成

> C36H37O18
757.1979802 (C:36, H:37, O:18)

如果想要使用这个特性,需要在R#终端上导入mzkit程序包模块:imports "formula" from "mzkit";

如果我们需要了解到这个天然产物在丢失一个coumaroylglucoside基团的时候,整个天然产物分子剩下部分的精确分子质量(二级质谱图中的m/z)是多少,则我们可以使用R#语言的这个化学式符号计算功能来完成。我们已经知道coumaroyl基团的原型为p-coumaric acid(C9H8O3),葡萄糖则是C6H12O6,那么中性丢失基团的化学式就是C6H12O6 + C9H8O3 - H2O。我们将上面的中性丢失化学计算表达式输入到R#解释器之中,按下回车

> C6H12O6 + C9H8O3 - H2O
326.1001628 (C:15, H:18, O:8)

然后根据这个计算结果,我们就可以得到丢掉一个coumaroylglucoside中性基团的剩下部分Cyanidin 3-glucoside这个天然产物的元素组成了,将表达式C36H37O18 - C15H18O8 + H2O输入到R#解释器之中,按下回车

> C36H37O18 - C15H18O8 + H2O
449.1083816 (C:21, H:21, O:11)
> toString(C36H37O18 - C15H18O8 + H2O)
[1] "C21H21O11"

通过检索pubchem数据库,我们可以了解到Cyanidin 3-glucoside这个天然产物的化学式就是C21H21O11+,上面的化学符号计算结果完全正确。

Cyanidin 3-glucoside

这个化学式符号语法特性是如何实现的?

由于化学式都是以原子的字母开头的,并且化学式都是以字母和数字组成的,所以每一个化学式在R#语言之中,都是一个合法的标识符。在R#环境之中,存在着一个哈希表用来保存R#脚本运行时的符号列表,我们输入一个化学式的时候,如果之前没有在环境中赋值过,则肯定会在环境的符号哈希表中找不到这个化学式符号所表示的值对象。这个时候R#脚本解释器的符号查找函数就会检查符号语言列表,查看这个输入的符号字符串是否可以被解释为某种符号语言。如果解释失败的话,最终才会返回一个符号不存在的错误消息给用户。

当我们在加载mzkit模块的formula包的时候,R#脚本的解释器会自动导入一个化学式的符号解释器,这个符号解释器其实就是一个已经写好的化学式解析函数:其将输入的化学式字符串解释为化学式对象(包括原子数量,精确分子质量这两大信息)。

''' <summary>
''' Get atom composition from a formula string
''' </summary>
''' <param name="formula">The input formula string text.</param>
''' <returns></returns>
<ExportAPI("scan")>
<RSymbolLanguageMask("[a-zA-Z0-9()]+", True, Test:=GetType(TestFormulaSymbolLang))>
Public Function ScanFormula(formula$, Optional env As Environment = Nothing) As Formula
   Dim globalEnv As GlobalEnvironment = env.globalEnvironment
   Dim n As Integer = globalEnv.options.getOption("formula.polymers_n", 999)
   Dim formulaObj As Formula = FormulaScanner.ScanFormula(formula, n)

   Return formulaObj
End Function

从上面的代码片段可以看出,如果你需要在你自己所编写的R#程序包模块之中定义一个符号解释器的话,可以在对R#导出的api函数之上,添加一个RSymbolLanguageMask的自定义属性标记,然后函数的参数第一个参数必须要为字符串类型,用来接受从R#脚本解释器的符号查找函数中符号字符串数据。另一个R#的环境对象参数,则是可选的。通过这个参数可以让你从R#环境中的options配置得到一些额外的参数。

如何实现化学式的数学计算的?

通过上面的计算例子,你也许会惊奇的发现,向R#解释器中输入的化学式字符串之间居然是可以直接进行数学计算的。这个是因为在formula程序包所导出来的api函数之中,还存在着一些通过ROperator自定义属性标记的函数。这些函数就是起着formula对象之间的数学计算定义的功能,例如

<ROperator("+")>
Public Function add(part1 As Formula, part2 As Formula) As Formula
    Return part1 + part2
End Function

这个函数将会处理C15H18O8 + H2O这样的二元运算操作。那么这个加号运算的底层是如何实现的呢,继续在VisualStudio中查看mzkit的源代码,可以发现是通过formula类的加号运算符重载来实现的:这个加号运算就是将两个formula对象的原子元素组成进行相加即可。其他的符号运算也是依照同样的基本四则运算法则来实现,这个语言特性的基本实现原理非常的简单吧?

Public Shared Operator +(a As Formula, b As Formula) As Formula
    Dim newComposition = a.CountsByElement.Keys _
        .JoinIterates(b.CountsByElement.Keys) _
        .Distinct _
        .ToDictionary(Function(e) e,
                      Function(e)
                          Return a(e) + b(e)
                      End Function)

    Return New Formula(newComposition)
End Operator
Latest posts by xie guigang (see all)

Attachments

3 Responses

  1. […] 我们通过学习使用ggplot过程中了解到,ggplot是通过加法运算来进行可视化图层的叠加以及样式调整等操作。那在R#语言之中是如何实现上面所示的一个加法运算过程的呢?如果你在之前已经读过了我的这篇博客文章《化学式符号运算》,那心中应该是存在有答案了的。我们在这里任然是通过使用ROperator自定义属性来标记我们的函数,实现上面的加法运算: […]

Leave a Reply

Your email address will not be published.