化合物から水素原子を引き抜いたラジカルの組み合わせ

ある化合物がCYPにより水酸化されて代謝されると分かっている場合に、全ての水素原子の乖離エネルギーを求めてラジカル生成エネルギーの低い順に代謝部位のあたりをつけたりしますが、毎回手で水素を消去した組み合わせを作るのは面倒なのでopenbabelでやってみます。

molオブジェクトのコピーをとるメソッドが見つからなかったのでcloneという関数を定義してます。

import openbabel as ob
import sys

def clone(mol):
   new_mol = ob.OBMol()
   for atom in ob.OBMolAtomIter(mol):
       new_mol.AddAtom(atom)

   for bond in ob.OBMolBondIter(mol):
       new_mol.AddBond(bond)
   return new_mol

def abs_h(mol):
   radicals = []
   mol.AddHydrogens()
   h_indexs = [atom.GetIdx() for atom in ob.OBMolAtomIter(mol) if
atom.GetType() == 'H']

   for h_index in h_indexs:
       new_mol = clone(mol)
       new_mol.DeleteAtom(new_mol.GetAtom(h_index))
       new_mol.SetTotalSpinMultiplicity(2)
       new_mol.SetTotalCharge(0)
       radicals.append(new_mol)
   return radicals


if __name__ == '__main__':
   input = sys.argv[1]

   obc = ob.OBConversion()
   obc.SetInAndOutFormats("smi", "mol")

   mol = ob.OBMol()
   next = obc.ReadFile(mol,input)

   while next:
       radicals = abs_h(mol)
       for r in radicals:
           print obc.WriteString(r)

       mol = ob.OBMol()
       next = obc.Read(mol)

あとはGamessでエネルギー計算すればいいですね。

GAMESSでさくっと構造最適化をしたい

forcefieldを用いた構造最適化の限界は、特に芳香環での部分電荷なんかに顕著に現れます。

そのため、必要最低限の量子化学計算のスキルはケミストには必須だと思います。しかし、量子化学計算ソフトウェアは歴史的経緯からか、ライトユーザーにとってはどうでもいい中間ファイルを出力したり、インプット自体が複雑怪奇だったりしますね?

そこで、そこら辺をまとめて面倒見つつ、必要最低限の作業をサクっとするようなものを書いてみました。

これを使うと、段々基底関数の精度をあげつつ最適化をするというルーチンがこんなかんじで書けます

import openbabel as ob
from gamess import Gamess

g = Gamess()

obc = ob.OBConversion()
obc.SetInFormat("mol")

methane = ob.OBMol()
next = obc.ReadFile(methane,"methane.mol")

# STO-3Gで構造最適化
methane_sto3g = g.optimize(methane, basis="sto3g")

# STO-3Gで最適化された構造を初期値にして、さらに6-31Gで最適化
methane_631g = g.optimize(methane_sto3g, basis="631g")

for a in ob.OBMolAtomIter(methane_631g):
    print "%s x: %1.3f, y: %1.3f, z: %1.3f charge: %1.3f" % \
    (a.GetType(), a.x(), a.y(), a.z(), a.GetPartialCharge())

実行結果

C3 x: 0.937, y: 0.067, z: 0.028 charge: -0.660
HC x: 2.019, y: 0.101, z: 0.086 charge: 0.165
HC x: 0.522, y: 0.935, z: 0.527 charge: 0.165
HC x: 0.578, y: -0.834, z: 0.512 charge: 0.165
HC x: 0.630, y: 0.066, z: -1.011 charge: 0.165

実際にユーザーが使いやすいようにするのはCGIのようなWEBから実行出来るようにしたほうがいいかもしれませんね。

アロマティックなヘテロ環を漏れなくさがす

MORPHというアロマティックなヘテロ環を探索するツールの論文があります。

結局、総当りで探していってバリデータで確認すればいいっぽいので、単環で探すようなものを書いてみました。

戦略としては単純で、まず総当りで評価するSMILES Stringを生成します。原子のタイプ(atomchoices)とボンドのタイプ(bondchoices)を別々に生成してからコンビネーションをとれば、全ての組み合わせがつくれます(もれなくだぶりありなのでミッシーではないですが)。

アロマティックなリングかどうかを判定するのに、アロマティックなリングは小文字になるという性質を使います。要するにopenbabelでSMILES文字列を読んで、SMILES文字列を出力させてみて小文字だったら、芳香環が生成できたと判断します。find_aromatic_ringsという部分です。

from pybel import *
import re

def atomchoices(n):
    if n == 1:
        return ["C","N","O"]
    else:
        return [atom+atoms for atoms in atomchoices(n-1) for atom in ["C","N","O"]]
       
def bondchoices(n):
    if n == 1:
        return ["-","="]
    else:
        return [bond+bonds for bonds in bondchoices(n-1) for bond in ["-","="]]

def make_smiles(n):
    atoms = atomchoices(n)
    bonds = bondchoices(n)
    atoms = [a for a in atoms if "C" in a]
    bonds = [b for b in bonds if b.count("=") / float(n) <= 0.5]

    for a in atoms:
        for b in bonds:
            smiles = ""
            for i in range(n):
                smiles = smiles + a[i]
                if i == 0: smiles += "1"
                smiles = smiles + b[i]               
                if i == n-1: smiles += "1"
            if "=O" not in smiles and "O=" not in smiles and "O-O" not in smiles:
                mymol = str(readstring("smi",smiles))[:-2]
                yield mymol

def find_aromatic_rings(n):
    rearoma = re.compile('^[a-z1-9]+$')
    aromatic_compounds = list(set([smi for smi in make_smiles(n) if rearoma.search(smi)]))
    print aromatic_compounds

縮合環でやるならそれ用のテンプレート文字列を渡せるようにするか内部で辞書的に持っておいてそれを置き換えるようにすればいいんじゃないかと思います。

ライブラリ中の特定の構造を削除する

サブストラクチャーサーチで集めたライブラリや、テンプレート構造を使って作ったバーチャルライブラリ群のプロパティを分析ししたいときに、テンプレート構造やサーチクエリに使った構造は要らない(むしろ邪魔)ことが多いと思います。

そういう場合プロパティの全平均からの差分と評価したりするのですが、構造自体を消してしまいたいこともあります。

import openbabel as ob
import sys

input = sys.argv[1]
 
obc = ob.OBConversion()
obc.SetInAndOutFormats("smi", "sdf")
 
mol = ob.OBMol()
next = obc.ReadFile(mol,input)

sp = ob.OBSmartsPattern()
sp.Init("c1ccccc1")

while next:
    sp.Match(mol);
    maplist = sp.GetUMapList()
    if len(maplist) > 0:
        del_atoms = []
        for i,atom in enumerate(ob.OBMolAtomIter(mol),1):
            if i in maplist[0]:
                del_atoms.append(atom)
        for a in del_atoms: mol.DeleteAtom(a)
    print obc.WriteString(mol)
    mol = ob.OBMol()
    next = obc.Read(mol)

これで例えばCOCc1ccccc1からベンゼン環を削除してCOCという構造だけを残すことができます。

GASTONで頻度よく現れる部分構造を探る

MCSは最大共通部分構造なので、頻度よく現れる部分構造を探したいときにはちょっと適切ではないのでGASTONを使うと良いのですが、GASTONは一般的なグラフに対して検索するようになっているので、化合物のデータはGASTONのファイル形式に変換してから計算を実行する必要があります。

また、実行結果もそのままでは解釈しにくいのでsdfやSMILESに変換しないといけません。

ちょっと面倒なので、そういったあたりをよろしく面倒みてくれるようにスクリプトを書いてみました。

http://gist.github.com/274906

OOな書き方にしたほうがテンポラリファイルの後処理とかちゃんとできるのでそのうち書き直す予定。

環構造を抽出する

PerlMolにはRing::Findというモジュールがあって、環のサーチをしてくれるのですが、返ってくるものが、アトムとボンドのIDの詰め合わせなのでちょっとあれです。本来ならば、見つかったリングの部分構造の分子オブジェクトのリストが返ってくると嬉しい場合も結構あります。

use warnings;
use strict;
use Chemistry::File::SMILES;
use Chemistry::Ring 'aromatize_mol';
use Chemistry::Ring::Find ':all';
use Perl6::Say;

sub get_rings {
  my $origin = shift;
  my @mols = ();
  my @rings = find_rings($origin, all => 1);

  for my $fg (@rings){
    my $mol = Chemistry::Mol->new(id => $fg->{id}, name => $fg->{id});
    for my $a (@{$fg->{atoms}}) { $mol->new_atom(name => $a->name, 
                                                 id => $a->id, symbol => $a->symbol) }
    for my $b (@{$fg->{bonds}}) { $mol->new_bond(name => $b->name, type => $b->type,
				atoms => [$mol->by_id($b->{atoms}->[0]->{id}),
		                $mol->by_id($b->{atoms}->[1]->{id})], order => $b->order) }
    $mol->add_implicit_hydrogens;
    aromatize_mol($mol);
    push @mols, $mol;
  }

  return @mols;
}

my $smi = "c1c(CC=O(O))cccc1CC2CC2CCC3CCCCCCC3O";
my $origin = Chemistry::Mol->parse($smi, format => 'smiles');
aromatize_mol($origin);

for my $mol (get_rings($origin)) {say $mol->print(format => 'smiles', unique => 1, name => 1);}