構造最適化シミュレーションをCytoscapeで
以前、おもむろに思いついたLeadOptimizationのシミュレーターで、networkxはgml出力できてCytoscapeでimportすればいいじゃんということに気づいてヤル気が出た。
ついでにコードもちょっと直して、活性はpIC50とかそういうレンジにしてみて、μMオーダーから上げていくようにした。

エッジは世代を表していて、黄色から緑にむけてすすんでいく。ノードの色は活性を表していて黒から赤になるにしたがって活性が向上する。最初の方は活性が振れるけど段々向上していって安定するようにしてみた。
リードホッピングとは選択されなかったより良い未来を選択しなおすことであるとするならばそのような希望はLO初期のあたりに存在すると思うのだ。
以下、書いたコード
from random import random
import networkx as nx
class Branch(object):
"""LeadOptimization flow
potency: pIC50 or such
weight : range for activity
"""
count = -1
@classmethod
def inc_count(cls):
cls.count = cls.count + 1
return cls.count
@classmethod
def get_count(cls): return cls.count
def __init__(self,potency,weight):
self.id = Branch.inc_count()
self.potency = potency
self.weight = weight
self.activity = self.potency + self.weight * random()
def make_child(self,num_childs,potency,weight):
return [Branch(self.potency + self.weight * (random()-0.5)*2,self.weight * 0.5) for i in range(num_childs)]
if __name__ == "__main__":
max_comps = 500 # total compounds
initial_branches = 1 # number of lead compounds
lead_potency = 5 # lead activity
generation = 0
G=nx.Graph()
heads = [Branch(lead_potency,2) for i in range(initial_branches)]
map(lambda b: G.add_node(b.id, activity=b.activity),heads)
while Branch.get_count() < max_comps:
new_heads = []
generation += 1
for branch in heads[:int(2+5*random())]:
for new_comp in branch.make_child(int(40*random()),branch.potency,branch.weight):
G.add_node(new_comp.id, activity=new_comp.activity)
G.add_edge(branch.id, new_comp.id, genneration=generation)
if new_comp.activity > 7:
new_heads.append(new_comp)
heads = new_heads
heads.sort(key=lambda x:x.activity)
heads.reverse()
nx.write_gml(G,"test.gml")
Sphinxで日本語pdfをつくる
rst2pdf で reStructuredText から PDF を生成するで日本語pdfが生成できるようになってもSphinxでは
[ERROR] pdfbuilder.py:120 BuildEnvironment instance has no attribute 'modules'
....
if self.config.pdf_use_modindex and self.env.modules:
AttributeError: BuildEnvironment instance has no attribute 'modules'
FAILED
とかいうエラーが出る(Sphinx1.02,1.03 + rst2pdf-0.15)
この場合にはrst2pdfの新しい版をsvnでインストール(0.16.dev-r2311をいれた)
あとはSphinxで日本語PDFを生成するの通りにやればうまくいく
中身は全然進んでないのに、出力だけは色々できるようになった ;-)
igraphを使ってmaximum common substructure(MCS)を求める
MCSを求めるには比較したい二つの化合物のModular Productを求めて、それの最大クリーク探索をすればいい。
Modular Productとは
- 二つのグラフの属性の対応が同じノードが両方共隣接していてエッジの属性が同じ場合
- 二つのグラフの属性の対応が同じノードが両方共隣接していない場合
にエッジを張ったグラフ。
これを作ってあとはigraphの最大クリーク探索メソッドで
import openbabel as ob
from igraph import *
sm1 = 'OCC(N)=O'
sm2 = 'O=CC(N)=O'
obc = ob.OBConversion()
obc.SetInAndOutFormats('smi','smi')
source = ob.OBMol()
obc.ReadString(source,sm1)
source_atoms = [a for a in ob.OBMolAtomIter(source)]
target = ob.OBMol()
obc.ReadString(target,sm2)
target_atoms = [a for a in ob.OBMolAtomIter(target)]
pairs = []
for i in range(len(source_atoms)):
for j in range(len(target_atoms)):
if source_atoms[i].GetAtomicNum() == target_atoms[j].GetAtomicNum():
pairs.append((source_atoms[i].GetIdx(),target_atoms[j].GetIdx()))
p = []
for x in range(len(pairs)-1):
for y in range(x+1,len(pairs)):
u = pairs[x]
v = pairs[y]
if u[0] != v[0] and u[1] != v[1]:
sb = source.GetBond(u[0],v[0])
tb = target.GetBond(u[1],v[1])
if sb != None: sbo = sb.GetBondOrder()
if tb != None: tbo = tb.GetBondOrder()
if (sb == None and tb == None) or (sb != None and tb != None and sbo == tbo):
p.append((pairs.index(u),pairs.index(v)))
g = Graph(p)
mc = g.largest_cliques()
for c in mc:
print [pairs[i] for i in c]
実際にはline graph(エッジとノードを入れ替えたグラフ)のModular Productをもとめてクリーク探索したほうが計算量がかなり減るらしい。
が、今回はやっていない。
Handbook of Chemoinformatics Algorithms (Chapman & Hall/Crc Mathematical and Computational Biology)Chapman & Hall / ¥ 8,895 ()
通常1~3週間以内に発送
Chemoinformatics and Computational Chemical Biology (Methods in Molecular Biology)Humana Press / ¥ 14,386 ()
近日発売 予約可
MCSSTanimotoで組んだネットワークのメリット
MCSSTanimotoで組んだネットワークのメリットはなんといってもエッジの属性にMCSの情報(smilesとかInChi)をのっけられることであろう。

import sys
from Gaston import MCSSTanimoto,Gaston
import openbabel as ob
obc = ob.OBConversion()
obc.SetInAndOutFormats("sdf", "smi")
input = "pc_sample.sdf"
mol = ob.OBMol()
next = obc.ReadFile(mol,input)
mols = [mol]
while next:
mol.StripSalts(14)
mols.append(mol)
mol = ob.OBMol()
next = obc.Read(mol)
mols = mols[1:11]
for i in range(len(mols)-1):
for j in range(i+1,len(mols)):
sys.stdout.flush()
mcs = MCSSTanimoto(mols[i],mols[j])
if mcs.score() > 0.2:
print "%s\t%s\t%s\t%s\t%2.3f" % (mols[i].GetTitle(), "mcs", \
mols[j].GetTitle(), obc.WriteString(mcs.mcs).split()[0], mcs.score())
でも、実際に組んでみたら、解釈が意外に難しそうなことに気がついた。MCSSTanimotoのアルゴリズム自体が問題なのかもしれない。
MCSよりはmolblaster的に細切れにしてみて最頻出のフラグメントからネットワークを組んでいくか、ありがち置換基の定義済みテーブルを使って類似性の高い置換基動どうしをつなぐという経験ベースのアプローチのほうが直感的かもしれないなぁ。
Pythonで外部コマンドがPATHに存在するか探す
SWIGでgastonのPythonバインディング作るのがいいのかもしれないけど、SWIG力が足りないので、外部コマンドから使っている。
os.environ['PATH']で探してみたけど、普通はどうやるんだろうか
if 'gaston' not in reduce(lambda a,b: a+b,[os.listdir(d) for d in \
os.environ['PATH'].split(':') if os.path.isdir(d)]):
print "gaston: command not found"
exit()
os.system("gaston %d %s %s > /dev/null 2>&1" % (freq,gasfile,output))
Gastonを使ってMCSを求める
openbabelにはMaximum Common Substructure(MCS)を求めるメソッドがないのでGastonを使った。
似たようなのは昔書いたけど扱いにくいのでクラスにしといた。ついでにMCSSTanimotoも求めるようにした。
MCSSTanimoto(A,B) = MCS /(A + B - MCS) #全てヘテロ原子数
という、MCS版のタニモト距離。例えばOc1ccccc1 とCc1ccccc1のMCSTanimotoは
6 /(7 + 7 - 6) = 0.75
スキャフォールドが決まっている場合には割と手軽に使えんじゃないかなぁ。まぁヘテロの性質無視して同一性だけで判断しているのでハロゲンの違いは割と似ているとかそういう補正は入れたほうがいいのかもしれないが。
import openbabel as ob
import os,re
from tempfile import mkstemp
class Gaston:
def __init__(self,mols=[]):
self.mols = mols
def writefile(self,filename):
""" convert file to gaston format"""
f = open(filename, 'w')
for (molnum, mol) in enumerate(self.mols):
f.write("t # %d\n" % molnum)
for i,atom in enumerate(ob.OBMolAtomIter(mol)):
f.write("v %d %d\n" % (i,atom.GetAtomicNum()))
for i,bond in enumerate(ob.OBMolBondIter(mol)):
f.write("e %d %d %d\n" % (bond.GetBeginAtomIdx()-1,bond.GetEndAtomIdx()-1,bond.GetBondOrder()))
return filename
def readfile(self,file):
txt = open(file).read()
p = re.compile('#.+?(?=(#|$))',re.S)
mols = p.finditer(txt)
result = [self._gas2ob(gas.group()) for gas in mols]
return result
def _gas2ob(self,gf):
mol = ob.OBMol()
for l in gf.split('\n'):
if len(l) > 0 and l[0] == 'v':
a = mol.NewAtom()
atomic_num = int(l.split(' ')[2])
a.SetAtomicNum(atomic_num)
elif len(l) > 0 and l[0] == 'e':
begin_atom_idx = int(l.split(' ')[1]) + 1
end_atom_idx = int(l.split(' ')[2]) + 1
bond_order = int(l.split(' ')[3])
b = mol.AddBond(begin_atom_idx, end_atom_idx, bond_order)
elif len(l) > 0 and l[0] == '#':
title = l.split(' ')[1]
mol.SetTitle(title)
return mol
def search(self,freq=None):
if freq == None: freq = len(self.mols)
(m,gasfile) = mkstemp()
(n,output) = mkstemp()
self.writefile(gasfile)
os.system("/opt/local/bin/gaston %d %s %s > /dev/null 2>&1" % (freq,gasfile,output))
mols = self.readfile(output)
os.unlink(gasfile)
os.unlink(output)
return mols
def mcs(self):
substructures = self.search()
mcs = substructures[0]
for substructure in substructures[1:]:
if substructure.NumAtoms() > mcs.NumAtoms():
mcs = substructure
elif substructure.NumAtoms() == mcs.NumAtoms():
if substructure.NumBonds() > mcs.NumBonds():
mcs = substructure
return mcs
class MCSSTanimoto:
def __init__(self,mol1,mol2):
self.mol1 = mol1
self.mol2 = mol2
def score(self):
mcs = Gaston(mols=[self.mol1,self.mol2]).mcs()
return float(mcs.NumAtoms()) / (mol1.NumAtoms() + mol2.NumAtoms() - mcs.NumAtoms())
if __name__ == '__main__':
import sys
obc = ob.OBConversion()
obc.SetInAndOutFormats("smi", "smi")
mol1 = ob.OBMol()
obc.ReadString(mol1, "CCC1=CC=CS1")
mol2 = ob.OBMol()
obc.ReadString(mol2, "OCC1=CC=CS1")
mol3 = ob.OBMol()
obc.ReadString(mol3, "CCCC1=CC=CS1")
gaston = Gaston(mols=[mol1,mol2,mol3])
print ":::Frequent Structures:::"
for m in gaston.search():
sys.stdout.write(obc.WriteString(m))
print ":::MCS:::"
sys.stdout.write(obc.WriteString(gaston.mcs()))
print ":::MCSSTanimoto:::"
score = MCSSTanimoto(mol1,mol2).score()
print score
実行結果
:::Frequent Structures:::
CC 3
CC=C 3
C(=C)C=C 3
C(=C)C=CS 3
CC(=C)S 3
CC=CC 3
CC(=CC)S 3
CC=CC=C 3
CC(=CC=C)S 3
Cc1cccs1 3
CC=CC=CS 3
CC=CS 3
CC=CSC 3
c1ccsc1 3
CC=C(SC)C 3
CC=CSCC 3
CCS 3
CCSC 3
CC(=C)SC 3
CCSC=C 3
CC(=C)SC=C 3
C=C 3
C=CS 3
C=CSC 3
C=CSC=C 3
CS 3
CSC 3
:::MCS:::
Cc1cccs1 3
:::MCSSTanimoto:::
0.75
あと実際にモジュール化する場合にはコマンドがどこにあるかわからないし、もしかしたたらインストールされてないかもしれないけど、そういう場合にどういう感じで処理すればいいんだろうか?whichで調べるとかすんのかな?
Flask+jQuery UIでAutocomplete
Flaskはjsonで返すメソッドがあるのでjQueryとの親和性は高そうな感じ。Autocomplete(suggest機能)が欲しかったので色々調べた結果、jQuery UIのがよさそうだったのでこれで。
GoogleのAJAX Libraries APIを使えばローカルにライブラリ置いておかなくてもよいので楽ちん。タグのリストはあえてJSONで取得するようなサンプルにしてみた。ちなみに実際に作っているモノはSQLiteでデータベースからselectした結果を返すようにしている。
下のコードだけで動く。
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def hello():
response = """<html><head>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/themes/base/jquery-ui.css" type="text/css" media="all" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/jquery-ui.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function() {$.getJSON("/tags", function(json){$("#tags").autocomplete({ source: json.tags});});});
</script></head>
<body><div class="demo"><div class="ui-widget"><label for="tags">Tags: </label><input id="tags"></div></div></body></html>
"""
return response
@app.route("/tags")
def show_tags():
tags = ["ActionScript", "AppleScript", "Asp", "BASIC", "C", "C++", "Clojure", "COBOL",
"ColdFusion", "Erlang", "Fortran", "Groovy", "Haskell", "Java", "JavaScript",
"Lisp", "Perl", "PHP", "Python", "Ruby", "Scala", "Scheme"];
return jsonify(tags = tags)
if __name__ == "__main__":
app.run()
実際に動かしてみたらタグを複数選択することが出来なくて、自分の欲しいものとはちょっと違うので、AutoSuggest使うかも。
あとautocompleteはリストじゃなくてURLを指定することもできるんだけど、Flask側でJSONのリストだけを返す方法がわからなくて困った。必ずハッシュで返ってきちゃうのかな?
化合物の類似性ネットワークのために閾値とedgeの数をプロットする。
昨日の化合物の類似性ネットワークのやつは何がメリットなのかなぁと考えてみたけど、一つにはedgeの数を自由に決められるあたりなんじゃないかと。あんまり低い閾値だとなんでもかんでもつながって何見てるかわからんし、逆に厳しい閾値にしちゃうとシングルトンばかりになっちゃうし。
というわけで、プロット欠かせてそれ見て閾値決める必要はあるんだろうなと。最終的にはeddgesの数とか、ノードあたりに生えているedgeの数の平均とかを統計的に処理して自動で閾値を決めるのが楽なんだろうけど、、、(一般的な方法あるのかな?)。X-meansに倣って、BICとかAIC使ってみようかなぁ。
で、プロット描いた。最初に書いたコードがナイーブすぎて遅くていらっときたので書き直した。まだかなり遅いけど、耐えられるのでこれでよしとした。
import pybel
mols = list(pybel.readfile("sdf", "pc_sample.sdf"))
fps = [x.calcfp() for x in mols]
similarity_matrix = []
for i in range(len(fps)):
for j in range(i+1,len(fps)):
similarity = fps[i] | fps[j]
similarity_matrix.append(similarity)
threshold = 0.0
print "threshold\tnodes"
while threshold < 100:
t = threshold / 100.0
new_matrix = [e for e in similarity_matrix if e > t]
nodes = len(new_matrix)
print "%2.1f\t%d" % (threshold, nodes)
similarity_matrix = new_matrix
threshold += 0.1
あとはRで
library(ggplot2)
nodes <- read.table("xxxx",header=T)
qplot(threshold,edgess, data=nodes, log="y")
dev.copy(device=png,filename="xxxxxx")
dev.off()

node数が1158なのでedgeの数が10000から100あたりの間でちょうどいい閾値を探したいんだけど、これだけじゃわからん。やっぱシングルトンの数とかノードあたりに生えているエッジ数の平均とか数えないとだめかな。
化合物の類似性でネットワークを構築してCytoscapeで見てみる
chemoinformaticsでもネットワークアナリシスは重要だと思う。今はマイナーだけど今後精力的に研究されんじゃないかなぁと。

pybel使うとシミラリティベースのネットワークは簡単に作れる。
import pybel
mols = list(pybel.readfile("sdf", "pc_sample.sdf"))
fps = [x.calcfp() for x in mols]
for i in range(len(fps)):
ids = []
for j in range(i,len(fps)):
if (fps[i] | fps[j]) > 0.5 and i != j:
ids.append(mols[j].title)
print "%s %s %s" % (mols[i].title, "sim", " ".join(ids))
SIF(simple interaction format)形式なので、拡張子sifでcytoscapeに読み込まさせればOK。
あとでケモインフォクックブックにも載せておこう。
Pythonで演算子のオーバーライド
openbabelのPythonのとこ見てたらFingerprintsのとこで
>>> print fps[0] | fps[1] # Print the Tanimoto coefficient
0.3333
タニモト距離を求めるように、メソッドオーバーライドしてんのかと。コードを眺めた。
class Fingerprint(object):
def __init__(self, fingerprint):
self.fp = fingerprint
def __or__(self, other):
return ob.OBFingerprint.Tanimoto(self.fp, other.fp)
@property
def bits(self):
return _findbits(self.fp, ob.OBFingerprint.Getbitsperint())
def __str__(self):
return ", ".join([str(x) for x in self.fp])
参考
Python クックブック 第2版Alex Martelli,Anna Martelli Ravenscroft,David Ascher
オライリー・ジャパン / ¥ 4,410 ()
在庫あり。



エキスパートPythonプログラミング