El siguiente script de Python lee todos los ficheros SDF que hemos descargado desde MolPort 2026-11-05 en nuestra ruta /home/jant.encinar/MolPortv9/SDF_original/ y genera en la ruta /home/jant.encinar/MolPortv9/sdf-500_v9/ ficheros SDF con 500 moléculas filtradas usando los criterios siguientes: 1) que exista una sola molécula (elimina fragmentos, sales, iones, etc), 2) peso molecular entre 400 y 750 Da, 3) compuestos con 2 o menos carbonos quirales, 4) extrae el nombre de MolPort del campo "PUBCHEM_EXT_DATASOURCE_REGID" y lo coloca en cada molecula seleccionada, 5) genera moléculas 3D y añade Hidrógenos.
import os
import glob
import time
import multiprocessing as mp
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
from rdkit.Chem.EnumerateStereoisomers import EnumerateStereoisomers, StereoEnumerationOptions
from rdkit import RDLogger
# --- 1. PATH DEFINITIONS (Edit these paths for your Linux server) ---
INPUT_DIR = "/home/jant.encinar/MolPortv9/SDF_original"
OUTPUT_DIR = "/home/jant.encinar/MolPortv9/sdf-500_v9"
CPUS_A_USAR = 32
# -------------------------------------------------------------------
def procesar_archivo(filepath):
"""
Funcion ejecutada en paralelo por cada nucleo.
Procesa un archivo SDF individual aplicando los filtros y generando estereoisomeros 3D.
"""
# Desactivar advertencias de RDKit en procesos hijos
RDLogger.DisableLog('rdApp.*')
filename = os.path.basename(filepath)
base_name = os.path.splitext(filename)[0]
supplier = Chem.ForwardSDMolSupplier(filepath, sanitize=True)
stereo_opts = StereoEnumerationOptions(tryEmbedding=True, maxIsomers=4)
file_counter = 1
mol_counter_in_file = 0
isomers_saved = 0
mols_evaluated = 0
def get_new_writer(counter):
out_path = os.path.join(OUTPUT_DIR, f"MPv9_{base_name}_{counter:04d}.sdf")
return Chem.SDWriter(out_path)
writer = get_new_writer(file_counter)
for mol in supplier:
mols_evaluated += 1
if mol is None:
continue
try:
# --- 1. FILTRO: ESTRUCTURA UNICA (Descartar mezclas, sales, agua, iones) ---
if len(Chem.GetMolFrags(mol)) > 1:
continue
# --- 2. FILTRO: PESO MOLECULAR (400 a 750 Da) ---
mw = Descriptors.MolWt(mol)
if not (400 <= mw <= 750):
continue
# --- 3. FILTRO: CARBONOS QUIRALES (Entre 1 y 2 carbonos quirales) ---
chiral_centers = Chem.FindMolChiralCenters(mol, includeUnassigned=True)
chiral_carbons = [c for c in chiral_centers if mol.GetAtomWithIdx(c[0]).GetSymbol() == 'C']
num_chiral = len(chiral_carbons)
if num_chiral < 1 or num_chiral > 2:
continue
# Obtencion de ID de PubChem si existe
base_id = mol.GetProp("PUBCHEM_EXT_DATASOURCE_REGID") if mol.HasProp("PUBCHEM_EXT_DATASOURCE_REGID") else f"MOL_{mols_evaluated}"
# Copia de los campos originales (, etc.)
props_originales = {prop: mol.GetProp(prop) for prop in mol.GetPropNames()}
# --- 4. GENERACION DE ESTEREOISOMEROS (Hasta 2^2 = 4) Y 3D ---
isomers = list(EnumerateStereoisomers(mol, options=stereo_opts))
for idx, isomer in enumerate(isomers):
isomer_name = f"{base_id}_{idx+1}"
isomer_h = Chem.AddHs(isomer)
# Reasignar campos de PubChem al nuevo objeto 3D
for k, v in props_originales.items():
isomer_h.SetProp(k, v)
# Generacion de coordenadas 3D
res = AllChem.EmbedMolecule(isomer_h, randomSeed=42)
if res != 0:
res = AllChem.EmbedMolecule(isomer_h, useRandomCoords=True, randomSeed=42)
if res != 0:
continue
# Optimizacion con MMFF
try:
AllChem.MMFFOptimizeMolecule(isomer_h, maxIters=200)
except Exception:
pass
# Asignacion de etiquetas R/S
try:
Chem.AssignStereochemistry(isomer_h, force=True, cleanIt=True)
centros_rs = Chem.FindMolChiralCenters(isomer_h)
if centros_rs:
texto_quiral = " | ".join([f"C{i}: {rs}" for i, rs in centros_rs])
isomer_h.SetProp("QUIRALIDAD", texto_quiral)
else:
isomer_h.SetProp("QUIRALIDAD", "No defined centers")
except Exception:
pass
isomer_h.SetProp("_Name", isomer_name)
# Escritura en el SDF de salida
try:
writer.write(isomer_h)
mol_counter_in_file += 1
isomers_saved += 1
except Exception:
continue
if mol_counter_in_file == 500:
writer.close()
file_counter += 1
writer = get_new_writer(file_counter)
mol_counter_in_file = 0
except Exception:
continue
if writer is not None:
writer.close()
# Eliminar archivo si quedo vacio
if mol_counter_in_file == 0:
last_file = os.path.join(OUTPUT_DIR, f"MPv9_{base_name}_{file_counter:04d}.sdf")
if os.path.exists(last_file):
os.remove(last_file)
return mols_evaluated, isomers_saved, filename
def main():
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
print("Starting search in multiprocess mode (32 CPUs)...")
start_time = time.time()
sdf_files = glob.glob(os.path.join(INPUT_DIR, "*.sdf"))
if not sdf_files:
print(f"No files found in: {INPUT_DIR}")
return
print(f"Files found: {len(sdf_files)}")
print("Background processing has started. Please wait...\n")
total_mols = 0
total_isomers = 0
with mp.Pool(CPUS_A_USAR) as pool:
for result in pool.imap_unordered(procesar_archivo, sdf_files):
mols_eval, iso_saved, fname = result
total_mols += mols_eval
total_isomers += iso_saved
print(f"File {fname} completed. (Added {iso_saved} isomers)")
elapsed_time = time.time() - start_time
print("-" * 50)
print("PROCESS COMPLETED SUCCESSFULLY IN MULTIPROCESSING!")
print(f"Initial molecules evaluated: {total_mols}")
print(f"Stereoisomers (3D+H) saved: {total_isomers}")
print(f"Total time: {elapsed_time/60:.2f} minutes.")
if __name__ == '__main__':
main()
# Justo debajo tenemos el sbatch. Esto no es parte del script...
#!/bin/bash
#SBATCH --job-name=MP9
#SBATCH --qos=long
#SBATCH --cpus-per-task=32
#SBATCH --mem=64G
#SBATCH --time=2-00:00:00
#SBATCH --output=/home/jant.encinar/MolPortv9/error/MP9_salida-%j.out
#SBATCH --error=/home/jant.encinar/MolPortv9/error/MP9_error-%j.err
# Cargar el entorno de Python/Conda necesario
source /home/jant.encinar/anaconda3/etc/profile.d/conda.sh
# Nombre de mi entorno con RDkit instalado
conda activate vina
# Ejecutar el script
python3 /home/jant.encinar/MolPortv9/molport_v9.py
Ejecutado en Linux los resultados han sido los siguientes:
Starting search in multiprocess mode (32 CPUs)...
Files found: 11
Background processing has started. Please wait...
File iissc-003-500-000--003-999-999.sdf completed. (Added 63534 isomers)
File iissc-000-500-000--000-999-999.sdf completed. (Added 81005 isomers)
File iissc-001-500-000--001-999-999.sdf completed. (Added 100714 isomers)
File iissc-002-500-000--002-999-999.sdf completed. (Added 119277 isomers)
File iissc-003-000-000--003-499-999.sdf completed. (Added 119340 isomers)
File iissc-004-500-000--004-999-999.sdf completed. (Added 97044 isomers)
File iissc-001-000-000--001-499-999.sdf completed. (Added 130649 isomers)
File iissc-004-000-000--004-499-999.sdf completed. (Added 108595 isomers)
File iissc-000-000-000--000-499-999.sdf completed. (Added 210164 isomers)
File iissc-005-000-000--005-499-999.sdf completed. (Added 101994 isomers)
File iissc-002-000-000--002-499-999.sdf completed. (Added 221236 isomers)
--------------------------------------------------
PROCESS COMPLETED SUCCESSFULLY IN MULTIPROCESSING!
Initial molecules evaluated: 5406724
Stereoisomers (3D+H) saved: 1353552
Total time: 1404.34 minutes.El acceso a la quimioteca en formato SDF (estrictamente con fines de investigación científica) está disponible en el siguiente enlace.