diff --git a/training/app.py b/training/app.py index ad5eb45..8ebae39 100644 --- a/training/app.py +++ b/training/app.py @@ -28,13 +28,14 @@ from fastapi.responses import HTMLResponse from pydantic import BaseModel from rdkit import Chem -from rdkit.Chem import AllChem, DataStructs +from rdkit.Chem import DataStructs, rdFingerprintGenerator import predict as P # reuse the unified flavor read app = FastAPI(title="Flavor Workbench (demo)") _FPS, _SMI, _KNOWN = [], [], [] +_MORGAN = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048) def _build_index(): @@ -48,7 +49,7 @@ def _build_index(): mol = Chem.MolFromSmiles(r["smiles"]) if mol is None: continue - _FPS.append(AllChem.GetMorganFingerprintAsBitVect(mol, 2, nBits=2048)) + _FPS.append(_MORGAN.GetFingerprint(mol)) _SMI.append(r["smiles"]) _KNOWN.append([t for t in basic if r[t] == 1]) print(f"substitution index built: {len(_FPS)} molecules") @@ -92,7 +93,7 @@ def api_neighbors(q: Query): if not smi or not _FPS: return {"neighbors": []} mol = Chem.MolFromSmiles(smi) - fp = AllChem.GetMorganFingerprintAsBitVect(mol, 2, nBits=2048) + fp = _MORGAN.GetFingerprint(mol) sims = DataStructs.BulkTanimotoSimilarity(fp, _FPS) self_smi = Chem.MolToSmiles(mol) ranked = sorted(range(len(sims)), key=lambda i: sims[i], reverse=True) diff --git a/training/predict.py b/training/predict.py index 559ae1f..eaf564a 100644 --- a/training/predict.py +++ b/training/predict.py @@ -48,9 +48,10 @@ import joblib import numpy as np from rdkit import Chem -from rdkit.Chem import AllChem, Crippen, DataStructs, Descriptors, rdMolDescriptors +from rdkit.Chem import Crippen, DataStructs, Descriptors, rdFingerprintGenerator, rdMolDescriptors FP_BITS, FP_RADIUS = 2048, 2 +_MORGAN = rdFingerprintGenerator.GetMorganGenerator(radius=FP_RADIUS, fpSize=FP_BITS) TASTE = Path("taste_models") ACID_SMARTS = { @@ -234,7 +235,7 @@ def _measured(mol): def _fp(mol): - bv = AllChem.GetMorganFingerprintAsBitVect(mol, FP_RADIUS, nBits=FP_BITS) + bv = _MORGAN.GetFingerprint(mol) arr = np.zeros((FP_BITS,), dtype=np.int8) DataStructs.ConvertToNumpyArray(bv, arr) return arr.reshape(1, -1) diff --git a/training/train_taste.py b/training/train_taste.py index 72a2c1e..36a4f80 100644 --- a/training/train_taste.py +++ b/training/train_taste.py @@ -21,7 +21,7 @@ import numpy as np import pandas as pd from rdkit import Chem -from rdkit.Chem import AllChem, DataStructs +from rdkit.Chem import DataStructs, rdFingerprintGenerator from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score, r2_score @@ -31,6 +31,7 @@ # handled in predict.py and never trained — regardless of how much data accrues. RULE_TASTES = {"sour", "salty"} FP_BITS, FP_RADIUS = 2048, 2 +_MORGAN = rdFingerprintGenerator.GetMorganGenerator(radius=FP_RADIUS, fpSize=FP_BITS) # Below this, a taste is too thin for an HONEST head, so it's skipped and # handled by rule/flag instead. It's not a hard exclusion: add more data (more # sources) and the taste crosses the line and trains itself on the next run. @@ -55,7 +56,7 @@ def fp(smiles): mol = Chem.MolFromSmiles(smiles) if mol is None: return None - bv = AllChem.GetMorganFingerprintAsBitVect(mol, FP_RADIUS, nBits=FP_BITS) + bv = _MORGAN.GetFingerprint(mol) arr = np.zeros((FP_BITS,), dtype=np.int8) DataStructs.ConvertToNumpyArray(bv, arr) return arr