理論創薬研究所の金子信人です。
今回はSMILESをもとに分子の反応性を予測するための官能基情報をマッピングするSMARTS-RXを紹介します。
参考論文
SMARTS-RX: a SMARTS-based representation of chemical functions for reactivity analysis
(Thierry Kogej, Christos Kannas, Samuel Genheden, Eike Caldeweyher & Mikhail Kabeshov, Journal of Cheminformatics, 2025, 17, 177.)
https://doi.org/10.1186/s13321-025-01136-8
SMILES(Simplified Molecular Input Line Entry System)は分子構造を文字列として表記する方法の一つですが、原子同士の隣接関係と結合次数のみで表記されます。しかしながら、合成反応における反応性の評価では官能基がどのような位置関係にあるかで反応性が大きく変わってきます。そこでSMILESにより多くの情報を記載できるように拡張したものがSMARTS(SMiles ARbitrary Target Specificaion)であり、部分構造検索などで用いられています。今回紹介するSMARTS-RXはSMARTSをベースに官能基を詳細に分類し、反応解析に活用するというものになります。
動作環境
Intel Core i7-1265U
Windows 11 Pro
conda 25.7.0
SMARTS-RXのインストール
Pythonの仮想環境を作成し、SMARTS-RXをインストールします。
推奨環境として指定されているPython 3.10で仮想環境を作成し、poetryをインストールします。
また、後程使用するのでpandasもインストールしておきます。
git cloneしたフォルダからpoetryでSMARTS-RXをインストールしました。
最後にデータベースを作成して完了です。
$ conda create -n smartsrx python=3.10 -y
$ conda activate smartsrx
$ conda install poetry=2.1.3 pandas
$ git clone https://github.com/MolecularAI/smartsrx.git
$ cd smartsrx
$ poetry install
$ python -m smartsrx.create_json
正しくインストールされていればDatabase created successfully!と表示されます。
SMARTS-RXの実行
SMARTS-RXはRDKitのHasSubstructMatchまたはGetSubstructMatches関数を使って実行されます。
作成した以下のスクリプトをsmartsrx.pyとして保存し、実行してみます。
import json
import os
from rdkit import Chem
from smartsrx import ReactiveFunctionDatabase
def load_database(json_path='smartsrx.json'):
"""JSONからデータベースを読み込む"""
if not os.path.exists(json_path):
raise FileNotFoundError(f"{json_path} が見つかりません。'python -m smartsrx.create_json' を実行してください。")
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return ReactiveFunctionDatabase(**data)
def get_molecule_class(mol, db):
"""
RDKitの分子(mol)に対し、データベース内の全SMARTSを適用し、マッチした官能基(ReactiveFunction)のリストを返す
"""
matches = []
# データベース内の全定義(dataリスト)を走査
for entry in db.data:
if not entry.smarts:
continue
# SMARTSパターンを作成
pattern = Chem.MolFromSmarts(entry.smarts)
# RDKitで部分構造マッチング判定
if pattern and mol.HasSubstructMatch(pattern):
matches.append(entry)
return matches
# メイン処理
if __name__ == "__main__":
# 1. データベースのロード
try:
db = load_database()
print(f"Database loaded (Version: {db.version})")
print(f"Total definitions: {len(db.data)}")
except Exception as e:
print(f"Error loading database: {e}")
exit()
# 2. 判定したい分子
smiles_list = [
("Benzoic Acid", "c1ccccc1C(=O)O"), # 芳香族カルボン酸
("Ethanol", "CCO"), # 第一級アルコール
("Acetone", "CC(=O)C") # ケトン
]
for name, smiles in smiles_list:
print(f"\n--- Analyzing: {name} ({smiles}) ---")
mol = Chem.MolFromSmiles(smiles)
if not mol:
print("Invalid SMILES")
continue
# 官能基を取得
found_roles = get_molecule_class(mol, db)
if found_roles:
for role in found_roles:
# テストコードにある属性名を使って表示
print(f"MATCH FOUND:")
print(f" Class : {role.category}")
print(f" Subclass : {role.subcategory}")
print(f" SMARTS-RX : {role.specific_type}")
print(f" SMARTS : {role.smarts}")
else:
print("No reactive classs found.")
実行結果は以下のように表示されます。
それぞれの化合物についてヒットしたClass, Subclass, SMARTS-RXと該当する原子のSMARTSが返されます。
安息香酸は芳香族カルボン酸、エタノールは第一級アルコール、アセトンはケトンとして認識されました。
Database loaded (Version: 1.0.0)
Total definitions: 406
--- Analyzing: Benzoic Acid (c1ccccc1C(=O)O) ---
MATCH FOUND:
Class : Acid
Subclass : Acid_Aromatic
SMARTS-RX : Acid_Aromatic
SMARTS : [O;D1;$(OC(=O)[c;$(c1ccccc1)])]
--- Analyzing: Ethanol (CCO) ---
MATCH FOUND:
Class : Alcohol
Subclass : PrimaryAlcoholAliphatic
SMARTS-RX : PrimaryAlcoholAliphatic
SMARTS : [O;H1;D1;$(O[C;D2,D1;!$(C[a])]);!$(OC=*);!$(OC#*)]
--- Analyzing: Acetone (CC(=O)C) ---
MATCH FOUND:
Class : Ketone
Subclass : KetoneAliphaticAcyclic
SMARTS-RX : KetoneAliphaticAcyclic
SMARTS : [#6;!R;$([#6;D3;$([#6](=O)([#6;A;!$(*=*)])[#6;A;!$(*=*)])]);!$([#6](=O)[C;D2][F,Cl,Br,I,$(OS(=O)(=O)C(F)(F)F)])]
続いて、csvからSMILESを取得し、任意の官能基を有する化合物を抽出するスクリプトを作成しました。
input.csvを以下のデータとして実行してみます。
| ID | SMILES |
| 1 | CCO |
| 2 | c1ccccc1C(=O)O |
| 3 | CC(=O)C |
| 4 | CC(C)O |
| 5 | CC(C)(C)O |
import pandas as pd
import json
import os
from rdkit import Chem
from smartsrx import ReactiveFunctionDatabase
JSON_DB_PATH = 'smartsrx.json'
INPUT_CSV = 'input.csv'
OUTPUT_CSV = 'output.csv'
def load_database(json_path):
"""データベースJSONを読み込む"""
if not os.path.exists(json_path):
print(f"エラー: {json_path} が見つかりません。")
print("'python -m smartsrx.create_json' を実行してください。")
return None
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return ReactiveFunctionDatabase(**data)
def find_matching_class(mol, db):
"""分子に対してマッチする全てのClassリストを返す"""
matches = []
# db.data はリスト形式
for entry in db.data:
if not entry.smarts:
continue
# SMARTSパターン作成
pattern = Chem.MolFromSmarts(entry.smarts)
# マッチング判定
if pattern and mol.HasSubstructMatch(pattern):
matches.append(entry)
return matches
def main():
# 1. データベースのロード
print(f"データベース ({JSON_DB_PATH}) を読み込んでいます...")
db = load_database(JSON_DB_PATH)
if db is None:
return
# 2. ユーザー入力 (検索キーワード)
print("-" * 50)
query = input("検索したい Class または Subclass を入力してください (部分一致): ").strip()
if not query:
print("検索ワードが空のため終了します。")
return
print(f"検索キーワード: '{query}' で解析を開始します...")
# 3. CSVの読み込み
if not os.path.exists(INPUT_CSV):
print(f"エラー: {INPUT_CSV} が見つかりません。")
return
try:
df = pd.read_csv(INPUT_CSV)
except Exception as e:
print(f"CSV読み込みエラー: {e}")
return
# SMILES列を特定する (大文字小文字を区別せず 'smiles' を探す、なければ1列目)
smiles_col = None
for col in df.columns:
if col.lower() == 'smiles':
smiles_col = col
break
if smiles_col is None:
smiles_col = df.columns[0] # 見つからなければ1列目を使用
print(f"警告: 'SMILES'という列名が見つかりません。1列目 '{smiles_col}' を使用します。")
# 4. 解析とフィルタリング
results = []
hit_count = 0
total_mols = len(df)
print(f"全 {total_mols} 件の分子を処理中...")
for index, row in df.iterrows():
smiles = row[smiles_col]
# 空データのスキップ
if pd.isna(smiles) or str(smiles).strip() == "":
continue
mol = Chem.MolFromSmiles(str(smiles))
if not mol:
# print(f"Invalid SMILES at row {index}: {smiles}")
continue
# 全マッチを取得
all_matches = find_matching_class(mol, db)
# 検索キーワードでフィルタリング
for match in all_matches:
# 大文字小文字を無視して比較
cat = match.category or ""
sub = match.subcategory or ""
spec = match.specific_type or ""
# Class(Category) または Subclass(Subcategory) にキーワードが含まれるか
if (query.lower() in cat.lower()) or (query.lower() in sub.lower()):
# 結果リストに追加 (元のCSVの情報 + ヒットした解析情報)
hit_data = row.to_dict()
hit_data['Matched_Class'] = cat
hit_data['Matched_Subclass'] = sub
hit_data['Matched_SMARTS-RX'] = spec
hit_data['Matched_SMARTS'] = match.smarts
results.append(hit_data)
hit_count += 1
# 5. 結果の保存
if results:
result_df = pd.DataFrame(results)
result_df.to_csv(OUTPUT_CSV, index=False, encoding='utf-8-sig')
print("-" * 50)
print(f"完了しました!")
print(f"ヒット件数: {len(results)} 件") # 1つの分子で複数ヒットする場合があるため行数は増えます
print(f"結果を '{OUTPUT_CSV}' に保存しました。")
else:
print("-" * 50)
print(f"キーワード '{query}' に一致する機能を持つ分子は見つかりませんでした。")
if __name__ == "__main__":
main()
上記のスクリプト実行すると、コンソール上で「検索したい Class または Subclass を入力してください (部分一致): 」と表示されます。”Alcohol”と入力するとoutput.csvに以下のように保存されます。
| ID | SMILES | Matched_Class | Matched_Subclass | Matched_SMARTS-RX | Matched_SMARTS | SMARTS |
| 1 | CCO | Alcohol | PrimaryAlcoholAliphatic | PrimaryAlcoholAliphatic | [O;H1;D1;$(O[C;D2,D1;!$(C[a])]);!$(OC=);!$(OC#)] | |
| 4 | CC(C)O | Alcohol | SecondaryAlcoholAliphaticAcyclic | SecondaryAlcoholAliphaticAcyclic | [O;H1;D1;$(OC;!R;D3[A])] | |
| 5 | CC(C)(C)O | Alcohol | TertiaryAlcoholAcyclic | TertiaryAlcoholAcyclic | [O;H1;D1;$(OC;!R;D4([a,A])[a,A])] |
エタノール、イソプロパノール、t-ブタノールがアルコールを有する化合物として抽出されました。
一方、”PrimaryAlcohol”と入力した場合にはエタノールのみが抽出されます。
おわりに
SMILESデータベースから化学反応を仮定して化合物ライブラリーを作成する際には反応する官能基の厳密な設定が不可欠でした。今回紹介したSMARTS-RXを用いればPrimaryAlcoholなどのわかりやすい用語でのフィルタリングが可能となり、バーチャルスクリーニングをより簡便に行うことができると思われます。
MoleculeResolverによる物質名からのSMILES検索
https://www.insilico.jp/blog/2025/10/09/moleculeresolver/
Category: AI創薬関連, RDKit