Canonical label その1

前回のつづき。
Canonical SMILES文字列を生成するためには、
まず、文字列の開始点となる原子を決定しなければなりません。


そのために、それぞれの原子がどのような環境にいるかを数値化して、
その順番に原子を並べ替える、というのがひとつの方法です。


Weiningerら*1は、以下の6種類の基準で数値化し、それらの数値を連結して
8桁の整数を生成して昇順に並べました。

  1. 結合の数
  2. 重原子との結合の数 (2桁)
  3. 原子番号 (2桁)
  4. 電荷の符号
  5. 電荷の絶対値
  6. 水素原子との結合の数

こうすることで、たとえば、エーテル分子(C-C-O-C-C)の場合、
10106003--20206002--20208000--20206002--10106003
という数値列に置き換えられるので、
最小値の"10106003"に対応する原子から始めれば良いということになります。
ただし、最小値が2ヶ所以上にある場合は、
Morgan法のように、隣接原子の順位を反映させつつ並び替えを繰り返します。
(この話は長くなりそうなので、今回は一旦ここで止めておきます)


さて、CDKでは、CanonicalLabelerクラスとして、一連のラベル付け機能が実装されています。
Weiningerらの表現が曖昧なので、実装によって結果も変わってきます。
CDKとOpenBabelの出力結果の違いも、この辺に原因がありそうです。

  • 二重/三重結合をどう数えるか
  • 電荷の符号は、形式電荷を使うか、部分電荷を使うか

以下は、CDKを土台としたサンプルコードですが、少し変更を加えています。


/* inv_label.java */

import java.io.*;
import org.openscience.cdk.interfaces.*;
import org.openscience.cdk.io.iterator.IteratingMDLReader;
import org.openscience.cdk.DefaultChemObjectBuilder;

import org.openscience.cdk.smiles.SmilesGenerator;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;

import org.openscience.cdk.smiles.InvPair;
import org.openscience.cdk.tools.periodictable.PeriodicTable;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;

import java.util.*;


class inv_label {

public static void main(String args[]){

if(args.length!=1){
System.err.println("inv_label <sd-file>");
System.exit(1);
}
FileInputStream fis = null;
IteratingMDLReader isr = null;
try{
fis = new FileInputStream(new File(args[0]) );
isr = new IteratingMDLReader(fis, DefaultChemObjectBuilder.getInstance() );
} catch (Exception e) {
e.printStackTrace();
}

while( isr.hasNext() ){
IMolecule imol = (IMolecule)isr.next();
String id = (String)imol.getProperty("cdk:Title");

StringBuffer inv;
ArrayList vect = new ArrayList();
IAtomContainer ac;

try {
AtomContainerManipulator acm = new AtomContainerManipulator();
ac = acm.removeHydrogens(imol);
for (int i = 0 ; i < ac.getAtomCount(); i++){
IAtom a = ac.getAtom(i);
inv = new StringBuffer();
Integer ddd = a.getImplicitHydrogenCount();

// 1. Num connections (incl H)
inv.append( ac.getConnectedAtomsList(a).size() +
(a.getImplicitHydrogenCount() == CDKConstants.UNSET ?
0 : a.getImplicitHydrogenCount()) );

// 2. Num of non H bonds
inv.append( String.format("%02d",
ac.getConnectedAtomsList(a).size()) );

// 3. Atomic num
inv.append( String.format("%02d",
PeriodicTable.getAtomicNumber(a.getSymbol()) ) );

// 4. Sign of charge
Integer fcharge = a.getFormalCharge();
if (fcharge == CDKConstants.UNSET) fcharge = 0;
if (fcharge < 0){
inv.append(1);
} else {
inv.append(0);
}

// 5. Absolute charge
inv.append( (int)Math.abs( (a.getFormalCharge() ==
CDKConstants.UNSET ? 0.0 : a.getFormalCharge()) ) );

// 6. Hydrogen count
inv.append( (a.getImplicitHydrogenCount() == CDKConstants.UNSET ?
0 : a.getImplicitHydrogenCount()));

vect.add(new InvPair(Long.parseLong(inv.toString()), a));
}
}catch (Exception e1) {
e1.printStackTrace();
}
for (int i = 0 ; i < vect.size() ; i++){
System.out.printf("%s\t%d\t%s\n", id, i, vect.get(i));
}
}
}
}

*1: Weininger et al. SMILES. 2. Algorithm for Generation of Unique SMILES Notation. J. Chem. Inf. Comput. Sci. (1989) 29, 97-101