"""Matched COSMO epoch 10 / Monroe + Kumo Large on official OpenADMET splits.""" from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parent)) import eval_common as common from eval_common import WORK, sha, write import concurrent.futures import fcntl import gc import hashlib import importlib.metadata import json import multiprocessing as mp import os import shutil import subprocess import time import traceback import numpy as np import pandas as pd import torch import wandb from huggingface_hub import HfApi, hf_hub_download, CommitOperationAdd from rdkit import Chem OUT = WORK/'openadmet_evaluation' REPOSITORY = 'Queimo/rem3di-pcqm-epoch2-cosmo' DATASET = 'openadmet/openadmet-expansionrx-challenge-data' DATA_REVISION = '6b898ccc43d10d25b230fb09e22a6e30c30022b5' MONROE = WORK/'external/Monroe' MONROE_REVISION = 'de075eabe98e4adbbedbc86ca9e877f4bfd9b629' MONROE_SHA = 'df996e8e98b12a3cda5fe5acbef3e1e9a4ea31946d3736461cd07e53a5bf205a' SEED = 42 PROTOCOL = dict( dataset=DATASET, dataset_revision=DATA_REVISION, reference_notebook=f'https://github.com/blazejba/Monroe/blob/{MONROE_REVISION}/examples/openadmet.ipynb', split='Original organizer train/test CSVs: 5326/2282; no new CV or test-based selection', representations=['cosmo_epoch10','monroe'], methods=['kumo_large'], expected_endpoint_results=18, labels='Raw numeric endpoint values, matching the reference notebook; only missing measurements excluded per endpoint', geometry='One author Monroe ETKDG/MMFF94s/UFF conformer per unique molecule, cached and shared exactly by both encoders', encoder='Frozen released Monroe encoder (720 dimensions) and frozen COSMO epoch 10 backbone (640 dimensions); strict state loading', monroe_revision=MONROE_REVISION, monroe_weights_sha256=MONROE_SHA, kumo=dict(size='large',num_estimators=4,seed=SEED,max_columns=640,estimator_batch_size=1, precision='float16 autocast',query_chunk_size=256, preprocessing='Default recipe fitted only on measured training rows; processed context shapes recorded'), metrics=['rmse','mae','r2','spearman'], comparison='Same molecules, conformers, endpoint masks, seeds and Kumo recipe; only representation differs') def status(phase, **kw): write(OUT/'status.json',dict(phase=phase,pid=os.getpid(),updated_unix=time.time(),**kw)) print('OPENADMET_STATUS',phase,json.dumps(kw),flush=True) def prepare_data(): status('preparing_official_dataset') parts = [] for split in ['train','test']: name = f'expansion_data_{split}.csv' source = Path(hf_hub_download(DATASET,name,repo_type='dataset',revision=DATA_REVISION)) target = OUT/'data'/name if not target.exists(): shutil.copyfile(source,target) assert sha(source)==sha(target) parts.append(pd.read_csv(target)) train,test = parts assert len(train)==5326 and len(test)==2282 endpoints = [c for c in train if c not in ['Molecule Name','SMILES']] assert len(endpoints)==9 and list(train.columns)==list(test.columns) smiles = [str(s) for s in pd.unique(pd.concat([train.SMILES,test.SMILES]))] assert len(smiles)==7608 canonical = [{Chem.MolToSmiles(Chem.MolFromSmiles(s),isomericSmiles=True) for s in df.SMILES} for df in parts] assert not canonical[0]&canonical[1] assert all(np.isfinite(df[e].dropna().to_numpy(dtype=float)).all() for df in parts for e in endpoints) PROTOCOL['endpoints'] = endpoints PROTOCOL['dataset_files_sha256'] = {p.name:sha(p) for p in (OUT/'data').glob('*.csv')} PROTOCOL['checkpoint_sha256'] = {n:sha(common.checkpoint(10)/n) for n in ['encoder.pth','atomic_preprocessor.pth','geometric_preprocessor.pth']} manifest=json.loads((common.checkpoint(10)/'manifest.json').read_text()) for name,digest in PROTOCOL['checkpoint_sha256'].items():assert digest==manifest['files'][name]['sha256'] write(OUT/'protocol.json',PROTOCOL) write(OUT/'data/measurement_counts.json', {e:dict(train=int(train[e].notna().sum()),test=int(test[e].notna().sum())) for e in endpoints}) return train,test,endpoints,smiles def restore_monroe(): env={**os.environ,'GIT_LFS_SKIP_SMUDGE':'1'} MONROE.parent.mkdir(exist_ok=True) if not (MONROE/'.git').exists(): subprocess.run(['git','clone','--depth','1','https://github.com/blazejba/Monroe.git',str(MONROE)],check=True,env=env) revision=subprocess.check_output(['git','-C',str(MONROE),'rev-parse','HEAD'],text=True).strip() if revision!=MONROE_REVISION: subprocess.run(['git','-C',str(MONROE),'fetch','--depth','1','origin',MONROE_REVISION],check=True,env=env) subprocess.run(['git','-C',str(MONROE),'checkout',MONROE_REVISION],check=True,env=env) weights=MONROE/'checkpoint/weights.pt' if not weights.exists() or sha(weights)!=MONROE_SHA: temp=weights.with_suffix('.download') subprocess.run(['curl','-fL','--retry','4','-o',str(temp),f'https://media.githubusercontent.com/media/blazejba/Monroe/{MONROE_REVISION}/checkpoint/weights.pt'],check=True) assert sha(temp)==MONROE_SHA temp.replace(weights) sys.path.insert(0,str(MONROE)) def graph_job(item): index,smiles=item from monroe.eval.embed import _featurize_one from rdkit import rdBase rdBase.SeedRandomNumberGenerator(SEED+index) return _featurize_one(smiles) def prepare_graphs(smiles): import pyarrow as pa import pyarrow.parquet as pq path=OUT/'features/shared_graphs.parquet' if path.exists(): records=pq.read_table(path).to_pylist() assert [r['smiles'] for r in records]==smiles return [(r.pop('smiles'),r) for r in records] status('generating_shared_conformers',total=len(smiles)) with concurrent.futures.ProcessPoolExecutor(max_workers=8,mp_context=mp.get_context('fork')) as pool: built=list(pool.map(graph_job,enumerate(smiles),chunksize=8)) missing=[smiles[i] for i,v in enumerate(built) if v is None] write(OUT/'data/coverage.json',dict(requested=len(smiles),missing=missing)) assert not missing, f'Do not silently shrink the official split: {missing[:8]}' assert [s for s,_ in built]==smiles records=[dict(smiles=s,**{k:np.asarray(g[k]).tolist() for k in ['node_float','node_codes','edge_index','edge_codes','pos_rdkit']}) for s,g in built] pq.write_table(pa.Table.from_pylist(records),path,compression='zstd') write(OUT/'features/shared_geometry_manifest.json',dict(rows=len(smiles),sha256=sha(path), all_z_zero_layouts=sum(bool(np.all(np.asarray(g['pos_rdkit'])[:,2]==0)) for _,g in built), source_revision=MONROE_REVISION)) return built def monroe_embeddings(built,smiles): path=OUT/'features/monroe.npz' if path.exists(): z=np.load(path);assert z['smiles'].tolist()==smiles;return z['X'] status('embedding_monroe',total=len(built)) from monroe.eval.embed import to_pyg from monroe.model.ckpt import _build_encoder,_load_state_dict from torch_geometric.data import Batch hp=json.loads((MONROE/'checkpoint/config.json').read_text()) encoder=_build_encoder(hp).eval() state=_load_state_dict(MONROE/'checkpoint/weights.pt',device='cpu') encoder.load_state_dict({k[len('encoder.'):]:v for k,v in state.items() if k.startswith('encoder.')},strict=True) del state encoder=encoder.cuda().requires_grad_(False) X=np.empty((len(built),720),dtype=np.float32) with torch.inference_mode(): for start in range(0,len(built),8): batch=Batch.from_data_list([to_pyg(g) for _,g in built[start:start+8]]).to('cuda') emb,_=encoder(batch);X[start:start+len(emb)]=emb.float().cpu().numpy() if start%512==0:status('embedding_monroe',completed=start+len(emb),total=len(built)) assert np.isfinite(X).all() np.savez_compressed(path,X=X,smiles=np.asarray(smiles),weights_sha256=MONROE_SHA) del encoder;gc.collect();torch.cuda.empty_cache() return X def cosmo_embeddings(built,smiles): path=OUT/'features/cosmo_epoch10.npz' if path.exists(): z=np.load(path);assert z['smiles'].tolist()==smiles assert json.loads(str(z['checkpoint_sha256']))==PROTOCOL['checkpoint_sha256'] return z['X'] from collections import Counter from monroe.model.constants import NODE_FEAT_LIST_ONE_HOT from remedi.data_handling.sample import Sample,yield_molecules_collate_fn from build_author_mace_runtime import build_author_mace_runtime from threedscriptors.data_handling.sample import Sample as LegacySample columns=list(NODE_FEAT_LIST_ONE_HOT);j=columns.index('atomic_number') vocab=NODE_FEAT_LIST_ONE_HOT['atomic_number'];ptable=Chem.GetPeriodicTable() atoms=[];charge_counts={} for s,g in built: codes=np.asarray(g['node_codes'])[:,j] assert (codes