import%20marimo%0A%0A__generated_with%20%3D%20%220.23.5%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Imports%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20json%0A%20%20%20%20import%20logging%0A%20%20%20%20import%20joblib%0A%20%20%20%20import%20gc%0A%20%20%20%20import%20math%0A%20%20%20%20import%20warnings%0A%20%20%20%20from%20abc%20import%20ABC%2C%20abstractmethod%0A%20%20%20%20from%20pathlib%20import%20Path%0A%20%20%20%20from%20typing%20import%20Iterator%2C%20Literal%2C%20Optional%0A%20%20%20%20from%20urllib.request%20import%20urlretrieve%0A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20polars%20as%20pl%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20altair%20as%20alt%0A%20%20%20%20import%20pingouin%20as%20pg%0A%20%20%20%20import%20seaborn%20as%20sns%0A%0A%20%20%20%20from%20scipy%20import%20stats%0A%20%20%20%20from%20scipy.stats%20import%20spearmanr%0A%20%20%20%20from%20sklearn.metrics%20import%20(%0A%20%20%20%20%20%20%20%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20r2_score%2C%0A%20%20%20%20%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20f1_score%2C%0A%20%20%20%20%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20%20%20%20%20mean_squared_error%2C%0A%20%20%20%20%20%20%20%20precision_score%2C%0A%20%20%20%20%20%20%20%20recall_score%2C%0A%20%20%20%20%20%20%20%20roc_auc_score%2C%0A%20%20%20%20)%0A%20%20%20%20from%20sklearn.model_selection._split%20import%20_BaseKFold%20as%20BaseKFold%0A%20%20%20%20from%20statsmodels.stats.anova%20import%20AnovaRM%0A%20%20%20%20from%20statsmodels.stats.libqsturng%20import%20psturng%2C%20qsturng%0A%0A%20%20%20%20from%20sklearn.ensemble%20import%20RandomForestClassifier%2C%20RandomForestRegressor%0A%20%20%20%20import%20xgboost%20as%20xgb%0A%0A%20%20%20%20import%20torch%0A%20%20%20%20from%20torch%20import%20nn%2C%20optim%0A%20%20%20%20from%20torch.functional%20import%20F%0A%20%20%20%20from%20torch.utils.data%20import%20DataLoader%0A%0A%20%20%20%20import%20lightning%20as%20L%0A%20%20%20%20from%20lightning%20import%20pytorch%20as%20pyl%0A%20%20%20%20from%20lightning.pytorch.callbacks.early_stopping%20import%20EarlyStopping%0A%0A%20%20%20%20from%20chemprop%20import%20data%2C%20featurizers%2C%20models%0A%20%20%20%20from%20chemprop%20import%20nn%20as%20chemnn%0A%20%20%20%20from%20chemprop.data%20import%20BatchMolGraph%0A%20%20%20%20from%20chemprop.models%20import%20MPNN%0A%20%20%20%20from%20chemprop.nn%20import%20RegressionFFN%0A%20%20%20%20from%20rdkit.Chem%20import%20MolFromSmiles%0A%0A%20%20%20%20from%20rdkit%20import%20Chem%0A%20%20%20%20from%20rdkit.Chem.Scaffolds%20import%20MurckoScaffold%0A%0A%20%20%20%20from%20skfp.preprocessing%20import%20ConformerGenerator%2C%20MolFromSmilesTransformer%0A%20%20%20%20from%20skfp.fingerprints%20import%20(%0A%20%20%20%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20E3FPFingerprint%2C%0A%20%20%20%20%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20)%0A%0A%20%20%20%20import%20gzip%0A%20%20%20%20import%20shutil%0A%20%20%20%20import%20subprocess%0A%20%20%20%20import%20sys%0A%20%20%20%20import%20tempfile%0A%0A%20%20%20%20import%20pandas%20as%20pd%0A%20%20%20%20import%20matplotlib.patches%20as%20mpatches%0A%20%20%20%20from%20tqdm.auto%20import%20tqdm%0A%20%20%20%20from%20typing%20import%20Iterable%0A%0A%20%20%20%20from%20rdkit%20import%20DataStructs%0A%20%20%20%20from%20rdkit.DataStructs%20import%20ExplicitBitVect%0A%0A%20%20%20%20import%20optuna%0A%20%20%20%20optuna.logging.set_verbosity(optuna.logging.WARNING)%0A%0A%20%20%20%20from%20dotenv%20import%20load_dotenv%0A%20%20%20%20load_dotenv(Path(%22..%2F.env%22))%0A%0A%20%20%20%20from%20tabpfn%20import%20TabPFNClassifier%2C%20TabPFNRegressor%0A%20%20%20%20import%20smurff%0A%20%20%20%20import%20scipy.sparse%20as%20sp%0A%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20BaseKFold%2C%0A%20%20%20%20%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20%20%20%20%20E3FPFingerprint%2C%0A%20%20%20%20%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20Iterator%2C%0A%20%20%20%20%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20MolFromSmilesTransformer%2C%0A%20%20%20%20%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20Optional%2C%0A%20%20%20%20%20%20%20%20Path%2C%0A%20%20%20%20%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20RandomForestClassifier%2C%0A%20%20%20%20%20%20%20%20RandomForestRegressor%2C%0A%20%20%20%20%20%20%20%20TabPFNClassifier%2C%0A%20%20%20%20%20%20%20%20TabPFNRegressor%2C%0A%20%20%20%20%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20f1_score%2C%0A%20%20%20%20%20%20%20%20gc%2C%0A%20%20%20%20%20%20%20%20gzip%2C%0A%20%20%20%20%20%20%20%20json%2C%0A%20%20%20%20%20%20%20%20math%2C%0A%20%20%20%20%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20%20%20%20%20mean_squared_error%2C%0A%20%20%20%20%20%20%20%20mo%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20optuna%2C%0A%20%20%20%20%20%20%20%20pd%2C%0A%20%20%20%20%20%20%20%20pg%2C%0A%20%20%20%20%20%20%20%20pl%2C%0A%20%20%20%20%20%20%20%20plt%2C%0A%20%20%20%20%20%20%20%20precision_score%2C%0A%20%20%20%20%20%20%20%20psturng%2C%0A%20%20%20%20%20%20%20%20qsturng%2C%0A%20%20%20%20%20%20%20%20r2_score%2C%0A%20%20%20%20%20%20%20%20recall_score%2C%0A%20%20%20%20%20%20%20%20roc_auc_score%2C%0A%20%20%20%20%20%20%20%20shutil%2C%0A%20%20%20%20%20%20%20%20smurff%2C%0A%20%20%20%20%20%20%20%20sns%2C%0A%20%20%20%20%20%20%20%20sp%2C%0A%20%20%20%20%20%20%20%20spearmanr%2C%0A%20%20%20%20%20%20%20%20subprocess%2C%0A%20%20%20%20%20%20%20%20sys%2C%0A%20%20%20%20%20%20%20%20tempfile%2C%0A%20%20%20%20%20%20%20%20torch%2C%0A%20%20%20%20%20%20%20%20tqdm%2C%0A%20%20%20%20%20%20%20%20warnings%2C%0A%20%20%20%20%20%20%20%20xgb%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20json%2C%20np%2C%20subprocess%2C%20sys%2C%20tempfile)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20CheMeleon%20embedding%20subprocess%20script%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20CheMeleon%20(PyTorch)%20must%20run%20in%20an%20isolated%20subprocess%20to%20avoid%20the%0A%20%20%20%20%23%20OpenMP%20runtime%20collision%20with%20sklearn%20and%20smurff%20in%20the%20parent%20process.%0A%20%20%20%20%23%20The%20script%20is%20written%20once%20here%20and%20reused%20by%20all%20analysis%20cells.%0A%20%20%20%20_CHEMELEON_SCRIPT%20%3D%20%22%5Cn%22.join(%5B%0A%20%20%20%20%20%20%20%20%22import%20os%2C%20json%2C%20sys%2C%20numpy%20as%20np%22%2C%0A%20%20%20%20%20%20%20%20%22os.environ%5B'KMP_DUPLICATE_LIB_OK'%5D%20%3D%20'TRUE'%22%2C%0A%20%20%20%20%20%20%20%20%22from%20pathlib%20import%20Path%22%2C%0A%20%20%20%20%20%20%20%20%22import%20torch%22%2C%0A%20%20%20%20%20%20%20%20%22from%20chemprop%20import%20featurizers%22%2C%0A%20%20%20%20%20%20%20%20%22from%20chemprop%20import%20nn%20as%20chemnn%22%2C%0A%20%20%20%20%20%20%20%20%22from%20chemprop.models%20import%20MPNN%22%2C%0A%20%20%20%20%20%20%20%20%22from%20chemprop.nn%20import%20RegressionFFN%22%2C%0A%20%20%20%20%20%20%20%20%22from%20chemprop.data%20import%20BatchMolGraph%22%2C%0A%20%20%20%20%20%20%20%20%22from%20rdkit.Chem%20import%20MolFromSmiles%22%2C%0A%20%20%20%20%20%20%20%20%22smiles_file%2C%20out_train%2C%20out_test%20%3D%20sys.argv%5B1%5D%2C%20sys.argv%5B2%5D%2C%20sys.argv%5B3%5D%22%2C%0A%20%20%20%20%20%20%20%20%22with%20open(smiles_file)%20as%20f%3A%22%2C%0A%20%20%20%20%20%20%20%20%22%20%20%20%20data%20%3D%20json.load(f)%22%2C%0A%20%20%20%20%20%20%20%20%22mp_path%20%3D%20Path.home()%20%2F%20'.chemprop'%20%2F%20'chemeleon_mp.pt'%22%2C%0A%20%20%20%20%20%20%20%20%22ckpt%20%3D%20torch.load(mp_path%2C%20weights_only%3DTrue)%22%2C%0A%20%20%20%20%20%20%20%20%22mp%20%3D%20chemnn.BondMessagePassing(**ckpt%5B'hyper_parameters'%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22mp.load_state_dict(ckpt%5B'state_dict'%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22model%20%3D%20MPNN(mp%2C%20chemnn.MeanAggregation()%2C%20RegressionFFN(input_dim%3Dmp.output_dim))%22%2C%0A%20%20%20%20%20%20%20%20%22model.eval()%22%2C%0A%20%20%20%20%20%20%20%20%22feat%20%3D%20featurizers.SimpleMoleculeMolGraphFeaturizer()%22%2C%0A%20%20%20%20%20%20%20%20%22def%20embed(smiles)%3A%22%2C%0A%20%20%20%20%20%20%20%20%22%20%20%20%20bmg%20%3D%20BatchMolGraph(%5Bfeat(MolFromSmiles(s))%20for%20s%20in%20smiles%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22%20%20%20%20with%20torch.no_grad()%3A%22%2C%0A%20%20%20%20%20%20%20%20%22%20%20%20%20%20%20%20%20return%20model.fingerprint(bmg).numpy(force%3DTrue)%22%2C%0A%20%20%20%20%20%20%20%20%22np.save(out_train%2C%20embed(data%5B'train'%5D))%22%2C%0A%20%20%20%20%20%20%20%20%22np.save(out_test%2C%20%20embed(data%5B'test'%5D))%22%2C%0A%20%20%20%20%5D)%0A%20%20%20%20_CHEMELEON_SCRIPT_PATH%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemeleon_embed.py%22%0A%20%20%20%20_CHEMELEON_SCRIPT_PATH.write_text(_CHEMELEON_SCRIPT)%0A%0A%20%20%20%20def%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20smiles_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20smiles_test%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20prefix%3A%20str%20%3D%20%22chemeleon%22%2C%0A%20%20%20%20)%20-%3E%20tuple%5Bnp.ndarray%2C%20np.ndarray%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Generate%20CheMeleon%20embeddings%20for%20train%20and%20test%20SMILES%20in%20an%20isolated%0A%20%20%20%20%20%20%20%20subprocess%2C%20returning%20(X_train%2C%20X_test)%20as%20float32%20numpy%20arrays.%0A%0A%20%20%20%20%20%20%20%20Runs%20CheMeleon%20in%20a%20child%20process%20so%20PyTorch's%20libkmp%20never%20shares%20the%0A%20%20%20%20%20%20%20%20address%20space%20with%20sklearn%20or%20smurff%20in%20the%20parent.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20smiles_train%3A%20SMILES%20strings%20for%20the%20training%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20smiles_test%3A%20%20SMILES%20strings%20for%20the%20test%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20prefix%3A%20Prefix%20for%20the%20temp%20files%20written%20by%20this%20call.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20(X_train%2C%20X_test)%20of%20shape%20(n_train%2C%202048)%20and%20(n_test%2C%202048).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20smi_file%20%20%20%3D%20tmp%20%2F%20f%22%7Bprefix%7D_smiles.json%22%0A%20%20%20%20%20%20%20%20train_file%20%3D%20tmp%20%2F%20f%22%7Bprefix%7D_train%22%0A%20%20%20%20%20%20%20%20test_file%20%20%3D%20tmp%20%2F%20f%22%7Bprefix%7D_test%22%0A%20%20%20%20%20%20%20%20smi_file.write_text(json.dumps(%7B%22train%22%3A%20smiles_train%2C%20%22test%22%3A%20smiles_test%7D))%0A%20%20%20%20%20%20%20%20result%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20str(_CHEMELEON_SCRIPT_PATH)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20str(smi_file)%2C%20str(train_file)%2C%20str(test_file)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22CheMeleon%20subprocess%20failed%3A%5Cn%7Bresult.stderr%7D%22)%0A%20%20%20%20%20%20%20%20X_train%20%3D%20np.load(str(train_file)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20X_test%20%20%3D%20np.load(str(test_file)%20%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20for%20p%20in%20%5Bsmi_file%2C%20Path(str(train_file)%20%2B%20%22.npy%22)%2C%20Path(str(test_file)%20%2B%20%22.npy%22)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20return%20X_train%2C%20X_test%0A%0A%20%20%20%20return%20(chemeleon_embed%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20classes%0A%0A%20%20%20%20Four%20model%20types%20share%20a%20stable%20API%3A%0A%0A%20%20%20%20%60%60%60%0A%20%20%20%20model.train(train_df%2C%20target_col%2C%20task%2C%20**kwargs)%0A%20%20%20%20model.predict(df)%20%20%20%20%20%20%20%20%20%20-%3E%20np.ndarray%0A%20%20%20%20%60%60%60%0A%0A%20%20%20%20%7C%20Class%20%7C%20Backend%20%7C%20Input%20features%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60RandomForestModel%60%20%7C%20sklearn%20RF%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60BoostedTreesModel%60%20%7C%20XGBoost%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60TabPFNModel%60%20%7C%20TabPFN%20(in-context%20transformer)%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60MacauModel%60%20%7C%20Macau%20%2F%20Bayesian%20matrix%20factorization%20(smurff)%20%7C%20fingerprint%20column%20%7C%0A%20%20%20%20%7C%20%60ChempropModel%60%20%7C%20Chemprop%20v2%20MPNN%20from%20scratch%20%7C%20SMILES%20column%20%7C%0A%20%20%20%20%7C%20%60ChempropChemeleonModel%60%20%7C%20Chemprop%20v2%20fine-tuned%20from%20%5BCheMeleon%5D(https%3A%2F%2Fgithub.com%2FJacksonBurns%2Fchemeleon)%20backbone%20%7C%20SMILES%20column%20%7C%0A%0A%0A%20%20%20%20%60task%60%20is%20either%20%60%22regression%22%60%20or%20%60%22classification%22%60.%0A%20%20%20%20Classification%20%60predict()%60%20returns%20the%20probability%20of%20the%20positive%20class.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20pl)%3A%0A%20%20%20%20def%20extract_fp_matrix(df%3A%20pl.DataFrame%2C%20fp_col%3A%20str)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Extract%20a%202-D%20float32%20feature%20matrix%20from%20a%20fingerprint%20column.%0A%0A%20%20%20%20%20%20%20%20The%20column%20is%20expected%20to%20hold%20numpy%20arrays%20of%20equal%20length%20as%20produced%0A%20%20%20%20%20%20%20%20by%20generate_fingerprint.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20DataFrame%20with%20a%20fingerprint%20column.%0A%20%20%20%20%20%20%20%20%20%20%20%20fp_col%3A%20Column%20name.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%202-D%20array%20of%20shape%20(n_compounds%2C%20fp_size).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20return%20np.stack(df%5Bfp_col%5D.to_list()).astype(np.float32)%0A%0A%20%20%20%20return%20(extract_fp_matrix%2C)%0A%0A%0A%40app.cell%0Adef%20_(RandomForestClassifier%2C%20RandomForestRegressor%2C%20np)%3A%0A%20%20%20%20class%20RandomForestModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22Scikit-learn%20Random%20Forest%20model%20with%20a%20unified%20fit%2Fpredict%20interface.%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22classification%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20int%20%3D%20100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20int%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3A%20int%20%3D%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_features%3A%20str%20%7C%20float%20%3D%20%22sqrt%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3A%20float%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20class_weight%3A%20str%20%7C%20dict%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20int%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3A%20int%20%3D%20-1%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20(RandomForestClassifier)%20or%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22regression%22%20(RandomForestRegressor).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20Number%20of%20trees%20in%20the%20forest.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20Maximum%20depth%20of%20each%20tree.%20None%20grows%20trees%20until%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20leaves%20are%20pure%20or%20contain%20fewer%20than%20min_samples_split%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3A%20Minimum%20number%20of%20samples%20required%20to%20split%20an%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20internal%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3A%20Minimum%20number%20of%20samples%20required%20to%20be%20at%20a%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20leaf%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_features%3A%20Number%20of%20features%20to%20consider%20at%20each%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22sqrt%22%20(default)%2C%20%22log2%22%2C%20a%20float%20fraction%2C%20or%20an%20int%20count.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3A%20Fraction%20of%20training%20samples%20drawn%20for%20each%20tree%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(bootstrap).%20None%20uses%20all%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class_weight%3A%20Only%20meaningful%20for%20classification.%20%22balanced%22%20adjusts%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20weights%20inversely%20proportional%20to%20class%20frequencies.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Random%20seed%20for%20reproducibility.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3A%20Number%20of%20parallel%20jobs%20for%20fitting%20trees.%20-1%20uses%20all%20CPUs.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20pred_type%20is%20not%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20common_kwargs%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3Dn_estimators%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3Dmax_depth%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_split%3Dmin_samples_split%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3Dmin_samples_leaf%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_features%3Dmax_features%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_samples%3Dmax_samples%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3Drandom_state%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3Dn_jobs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20RandomForestClassifier(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20**common_kwargs%2C%20class_weight%3Dclass_weight%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20pred_type%20%3D%3D%20%22regression%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20RandomForestRegressor(**common_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pred_type%20must%20be%20either%20'classification'%20or%20'regression'%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20the%20model%20on%20the%20training%20data.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20Training%20feature%20matrix.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.fit(X_train%2C%20y_train)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20for%20the%20test%20set.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20Test%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Predicted%20probabilities%20(classification)%20or%20values%20(regression).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict_proba(X_test)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict(X_test)%0A%0A%20%20%20%20return%20(RandomForestModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20xgb)%3A%0A%20%20%20%20class%20BoostedTreesModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22XGBoost%20gradient-boosted%20tree%20model%20with%20a%20unified%20fit%2Fpredict%20interface.%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22classification%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20int%20%3D%20100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20int%20%3D%206%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3A%20float%20%3D%200.3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20subsample%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20gamma%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3A%20int%20%3D%2010%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20scale_pos_weight%3A%20float%20%3D%201.0%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20(XGBClassifier)%20or%20%22regression%22%20(XGBRegressor).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20Maximum%20number%20of%20boosting%20rounds.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3A%20Maximum%20depth%20of%20each%20tree.%20Deeper%20trees%20capture%20more%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20interactions%20but%20increase%20overfitting%20risk.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3A%20Step-size%20shrinkage%20(%CE%B7).%20Lower%20values%20require%20more%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20trees%20but%20generalise%20better.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subsample%3A%20Fraction%20of%20training%20rows%20sampled%20per%20tree%20(0%20%3C%20subsample%20%E2%89%A4%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3A%20Fraction%20of%20features%20sampled%20per%20tree.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3A%20Minimum%20sum%20of%20instance%20weights%20in%20a%20child%20node.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Higher%20values%20prevent%20learning%20on%20rare%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gamma%3A%20Minimum%20loss%20reduction%20required%20to%20make%20a%20further%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Acts%20as%20a%20regularisation%20threshold.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3A%20L1%20regularisation%20on%20leaf%20weights%20(increases%20sparsity).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3A%20L2%20regularisation%20on%20leaf%20weights%20(shrinks%20weights).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3A%20Stop%20training%20if%20validation%20metric%20does%20not%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20improve%20for%20this%20many%20consecutive%20rounds.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20scale_pos_weight%3A%20Ratio%20of%20negative%20to%20positive%20class%20counts.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Only%20meaningful%20for%20classification%3B%20set%20to%20n_neg%2Fn_pos%20for%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20imbalanced%20datasets.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20pred_type%20is%20not%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20common_kwargs%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3Dn_estimators%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_depth%3Dmax_depth%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%3Dlearning_rate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20subsample%3Dsubsample%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%3Dcolsample_bytree%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20min_child_weight%3Dmin_child_weight%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gamma%3Dgamma%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%3Dreg_alpha%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reg_lambda%3Dreg_lambda%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20early_stopping_rounds%3Dearly_stopping_rounds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tree_method%3D%22hist%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_jobs%3D-1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20xgb.XGBClassifier(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20**common_kwargs%2C%20scale_pos_weight%3Dscale_pos_weight%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20pred_type%20%3D%3D%20%22regression%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20xgb.XGBRegressor(**common_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pred_type%20must%20be%20either%20'classification'%20or%20'regression'%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray%2C%20X_val%3A%20np.ndarray%2C%20y_val%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20the%20model%20using%20training%20data%20with%20an%20evaluation%20set%20for%20early%20stopping.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20Training%20feature%20matrix.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20Validation%20feature%20matrix%20used%20for%20early%20stopping.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20Validation%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.fit(X_train%2C%20y_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20eval_set%3D%5B(X_val%2C%20y_val)%5D%2C%20verbose%3DFalse)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20for%20the%20test%20set.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20Test%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Predicted%20probabilities%20(classification)%20or%20values%20(regression).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict_proba(X_test)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict(X_test)%0A%0A%20%20%20%20return%20(BoostedTreesModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(TabPFNClassifier%2C%20TabPFNRegressor%2C%20np)%3A%0A%20%20%20%20class%20TabPFNModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20TabPFN%20(Tabular%20Prior-Fitted%20Networks)%20model%20with%20a%20unified%20fit%2Fpredict%20interface.%0A%0A%20%20%20%20%20%20%20%20TabPFN%20is%20a%20transformer%20pretrained%20on%20synthetic%20tabular%20datasets%20that%20performs%0A%20%20%20%20%20%20%20%20in-context%20learning%3A%20the%20entire%20training%20set%20is%20passed%20as%20a%20%22prompt%22%20at%0A%20%20%20%20%20%20%20%20inference%20time%2C%20making%20fit()%20essentially%20instantaneous.%0A%0A%20%20%20%20%20%20%20%20Because%20TabPFN%20operates%20on%20a%20numeric%20feature%20matrix%20it%20must%20be%20paired%20with%0A%20%20%20%20%20%20%20%20a%20pre-computed%20fingerprint%20column%20(via%20generate_fingerprint%20%2F%20extract_fp_matrix)%0A%20%20%20%20%20%20%20%20rather%20than%20raw%20SMILES%20strings.%0A%0A%20%20%20%20%20%20%20%20%23%23%20Size%20considerations%0A%0A%20%20%20%20%20%20%20%20The%20upstream%20pretraining%20limits%20are%20disabled%20by%20default%0A%20%20%20%20%20%20%20%20(ignore_pretraining_limits%3DTrue)%20so%20the%20model%20can%20handle%20the%20full%20training%0A%20%20%20%20%20%20%20%20sets%20used%20in%20CV.%20%20Very%20large%20training%20sets%20(%3E%20~10%20000%20rows)%20will%20increase%0A%20%20%20%20%20%20%20%20inference%20time%20and%20memory%20substantially%20because%20the%20full%20training%20matrix%20is%0A%20%20%20%20%20%20%20%20held%20in%20the%20transformer's%20context.%0A%0A%20%20%20%20%20%20%20%20Usage%3A%3A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20TabPFNModel(pred_type%3D%22regression%22%2C%20n_estimators%3D8)%0A%20%20%20%20%20%20%20%20%20%20%20%20model.train(X_train%2C%20y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20model.predict(X_test)%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20int%20%3D%208%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20int%20%3D%200%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ignore_pretraining_limits%3A%20bool%20%3D%20True%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20device%3A%20str%20%3D%20%22cpu%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22regression%22%20(TabPFNRegressor)%20or%20%22classification%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(TabPFNClassifier).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3A%20Number%20of%20ensemble%20forward%20passes.%20Higher%20values%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20improve%20stability%20at%20the%20cost%20of%20inference%20time.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Random%20seed%20for%20reproducibility.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ignore_pretraining_limits%3A%20When%20True%20(default)%2C%20disables%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20upstream%20row%20%2F%20feature%20count%20limits%20so%20the%20model%20can%20be%20used%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20on%20full-sized%20training%20sets.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%3A%20PyTorch%20device.%20%20Defaults%20to%20%22cpu%22%20%E2%80%94%20MPS%20causes%20OOM%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors%20on%20the%20full%20CV%20training%20sets%20(~3%20300%20samples)%20due%20to%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20the%208.3%20GB%20MPS%20allocator%20ceiling%20on%20Apple%20Silicon.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20pred_type%20is%20not%20%22regression%22%20or%20%22classification%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20common_kwargs%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%3Dn_estimators%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20random_state%3Drandom_state%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ignore_pretraining_limits%3Dignore_pretraining_limits%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20device%3Ddevice%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22regression%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20TabPFNRegressor(**common_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.model%20%3D%20TabPFNClassifier(**common_kwargs)%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20TabPFN%20on%20the%20training%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Unlike%20gradient-boosted%20or%20neural%20models%2C%20TabPFN%20stores%20the%20training%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20internally%20and%20performs%20no%20iterative%20optimisation.%20%20fit()%20returns%0A%20%20%20%20%20%20%20%20%20%20%20%20almost%20immediately%3B%20the%20actual%20computation%20happens%20during%20predict().%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%202-D%20float32%20feature%20matrix%20of%20shape%20(n_train%2C%20n_features).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%201-D%20array%20of%20training%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model.fit(X_train%2C%20y_train)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20for%20the%20test%20feature%20matrix.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%202-D%20float32%20feature%20matrix%20of%20shape%20(n_test%2C%20n_features).%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20For%20regression%3A%201-D%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20For%20classification%3A%201-D%20array%20of%20predicted%20probabilities%20for%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20positive%20class%20(index%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict_proba(X_test)%5B%3A%2C%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20self.model.predict(X_test)%0A%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(np%2C%20smurff%2C%20sp%2C%20tempfile)%3A%0A%20%20%20%20class%20MacauModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Bayesian%20matrix%20factorization%20model%20using%20the%20Macau%20algorithm%20(via%20smurff).%0A%0A%20%20%20%20%20%20%20%20Macau%20extends%20BPMF%20(Bayesian%20Probabilistic%20Matrix%20Factorization)%20with%0A%20%20%20%20%20%20%20%20side%20information%3A%20molecular%20fingerprints%20are%20used%20as%20row-side%20features%2C%0A%20%20%20%20%20%20%20%20linking%20the%20latent%20compound%20factors%20to%20the%20chemical%20structure%20via%20a%0A%20%20%20%20%20%20%20%20learned%20link%20matrix.%20%20The%20model%20is%20fully%20Bayesian%20%E2%80%94%20predictions%20are%20the%0A%20%20%20%20%20%20%20%20average%20of%20posterior%20Gibbs%20samples%20after%20a%20burn-in%20phase.%0A%0A%20%20%20%20%20%20%20%20Because%20the%20model%20operates%20on%20a%20numeric%20feature%20matrix%20it%20must%20be%20paired%0A%20%20%20%20%20%20%20%20with%20a%20pre-computed%20fingerprint%20column%20(via%20generate_fingerprint%20%2F%0A%20%20%20%20%20%20%20%20extract_fp_matrix)%20rather%20than%20raw%20SMILES%20strings.%0A%0A%20%20%20%20%20%20%20%20%23%23%20How%20it%20works%0A%0A%20%20%20%20%20%20%20%20Training%20data%20is%20represented%20as%20a%20sparse%20matrix%20Y%20of%20shape%0A%20%20%20%20%20%20%20%20(n_compounds%2C%201).%20%20The%20fingerprint%20matrix%20(n_compounds%2C%20n_features)%20is%0A%20%20%20%20%20%20%20%20passed%20as%20row%20side%20information.%20%20During%20MCMC%20sampling%20smurff%20jointly%0A%20%20%20%20%20%20%20%20infers%3A%0A%0A%20%20%20%20%20%20%20%20-%20latent%20compound%20factors%20U%20%20(num_latent%20%C3%97%20n_compounds)%0A%20%20%20%20%20%20%20%20-%20latent%20target%20factor%20%20V%20%20(num_latent%20%C3%97%201)%0A%20%20%20%20%20%20%20%20-%20link%20matrix%20Beta%20%20connecting%20fingerprints%20to%20U%0A%0A%20%20%20%20%20%20%20%20Prediction%20for%20new%20compounds%20uses%20their%20fingerprints%20to%20impute%20U%20via%0A%20%20%20%20%20%20%20%20Beta%2C%20then%20computes%20U%20%C2%B7%20V.%0A%0A%20%20%20%20%20%20%20%20Usage%3A%3A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20MacauModel(num_latent%3D16%2C%20nsamples%3D200%2C%20burnin%3D100)%0A%20%20%20%20%20%20%20%20%20%20%20%20model.train(X_train_fp%2C%20y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20model.predict(X_test_fp)%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20num_latent%3A%20int%20%3D%2016%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20burnin%3A%20int%20%3D%20100%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20nsamples%3A%20int%20%3D%20200%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20univariate%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20direct%3A%20bool%20%3D%20True%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20num_threads%3A%20int%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20int%20%3D%2042%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20num_latent%3A%20Number%20of%20latent%20dimensions%20for%20the%20factorization.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Higher%20values%20capture%20more%20complex%20structure%20but%20increase%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20computation%20and%20overfitting%20risk.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20burnin%3A%20Number%20of%20Gibbs%20iterations%20to%20discard%20as%20burn-in%20before%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20collecting%20posterior%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nsamples%3A%20Number%20of%20posterior%20Gibbs%20samples%20to%20collect.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Predictions%20are%20averaged%20across%20these%20samples.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20univariate%3A%20When%20True%2C%20use%20the%20faster%20univariate%20sampler%20instead%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20of%20the%20joint%20multivariate%20sampler.%20%20Useful%20for%20very%20large%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20datasets%3B%20may%20converge%20more%20slowly.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20direct%3A%20When%20True%2C%20use%20the%20Cholesky%20(direct)%20solver%20for%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20link%20matrix%3B%20otherwise%20use%20conjugate%20gradient%20(CG).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Direct%20is%20faster%20for%20small%20side-info%20matrices%3B%20CG%20scales%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20better%20to%20very%20high-dimensional%20fingerprints.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20num_threads%3A%20Number%20of%20OpenMP%20threads.%20%20None%20lets%20smurff%20decide.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20Random%20seed%20for%20the%20Gibbs%20sampler.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.num_latent%20%20%3D%20num_latent%0A%20%20%20%20%20%20%20%20%20%20%20%20self.burnin%20%20%20%20%20%20%3D%20burnin%0A%20%20%20%20%20%20%20%20%20%20%20%20self.nsamples%20%20%20%20%3D%20nsamples%0A%20%20%20%20%20%20%20%20%20%20%20%20self.univariate%20%20%3D%20univariate%0A%20%20%20%20%20%20%20%20%20%20%20%20self.direct%20%20%20%20%20%20%3D%20direct%0A%20%20%20%20%20%20%20%20%20%20%20%20self.num_threads%20%3D%20num_threads%0A%20%20%20%20%20%20%20%20%20%20%20%20self.seed%20%20%20%20%20%20%20%20%3D%20seed%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Stored%20after%20train()%20so%20predict()%20can%20reuse%20the%20session%0A%20%20%20%20%20%20%20%20%20%20%20%20self._predict_session%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20def%20train(self%2C%20X_train%3A%20np.ndarray%2C%20y_train%3A%20%22np.ndarray%20%7C%20sp.spmatrix%22)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fit%20the%20Macau%20model%20via%20Gibbs%20sampling.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Accepts%20either%20a%201-D%20target%20array%20(single-task)%20or%20a%20pre-built%20sparse%0A%20%20%20%20%20%20%20%20%20%20%20%20(n%20%C3%97%20k)%20matrix%20(multitask).%20%20In%20the%20single-task%20case%20a%20sparse%20(n%20%C3%97%201)%0A%20%20%20%20%20%20%20%20%20%20%20%20COO%20matrix%20is%20constructed%20automatically%2C%20omitting%20any%20NaN%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20MacauSession%20uses%20X_train%20as%20row%20side%20information.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%202-D%20float32%20fingerprint%20matrix%2C%20shape%20(n_train%2C%20n_features).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%201-D%20target%20array%2C%20or%20sparse%20(n_train%20%C3%97%20k)%20target%20matrix.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sp.issparse(y_train)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Y_train%20%3D%20y_train.astype(np.float64)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n%20%3D%20len(y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_vals%20%3D%20y_train.flatten().astype(np.float64)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_mask%20%3D%20~np.isnan(_vals)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Y_train%20%3D%20sp.coo_matrix(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(_vals%5B_mask%5D%2C%20(np.where(_mask)%5B0%5D%2C%20np.zeros(_mask.sum()%2C%20dtype%3Dint)))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shape%3D(n%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20tempfile.TemporaryDirectory()%20as%20tmpdir%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20import%20os%2C%20logging%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_name%20%3D%20os.path.join(tmpdir%2C%20%22smurff_model.hdf5%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Silence%20smurff%3A%20verbose%3D0%20quiets%20the%20C%2B%2B%20layer%3B%20we%20also%20raise%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20the%20root%20logger%20threshold%20to%20suppress%20the%20Python-side%20INFO%20lines.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20We%20bypass%20session.run()%20entirely%20(which%20would%20create%20a%20tqdm%20bar)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20and%20call%20init()%20%2B%20step()%20directly%20instead.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smurff_logger%20%3D%20logging.getLogger(%22smurff%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_root_logger%20%20%20%3D%20logging.getLogger()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_prev_smurff%20%20%20%3D%20_smurff_logger.level%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_prev_root%20%20%20%20%20%3D%20_root_logger.level%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smurff_logger.setLevel(logging.ERROR)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_root_logger.setLevel(logging.ERROR)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20session%20%3D%20smurff.MacauSession(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Ytrain%3DY_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20side_info%3D%5BX_train.astype(np.float64)%2C%20None%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20num_latent%3Dself.num_latent%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20burnin%3Dself.burnin%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nsamples%3Dself.nsamples%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20univariate%3Dself.univariate%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20direct%3Dself.direct%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20num_threads%3Dself.num_threads%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3Dself.seed%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20verbose%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_name%3Dsave_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save_freq%3D1%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Manual%20loop%20%E2%80%94%20equivalent%20to%20session.run()%20but%20without%20tqdm%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20session.init()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20while%20session.step()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pass%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20finally%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smurff_logger.setLevel(_prev_smurff)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_root_logger.setLevel(_prev_root)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self._predict_session%20%3D%20session.makePredictSession()%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Generate%20predictions%20by%20averaging%20posterior%20Gibbs%20samples.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Uses%20the%20fingerprint%20matrix%20of%20test%20compounds%20as%20row%20side%20information%0A%20%20%20%20%20%20%20%20%20%20%20%20to%20project%20into%20the%20latent%20space%20and%20compute%20the%20predicted%20target%20value.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%202-D%20float32%20fingerprint%20matrix%2C%20shape%20(n_test%2C%20n_features).%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20array%20of%20predicted%20values%2C%20averaged%20across%20all%20posterior%20samples.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20RuntimeError%3A%20If%20called%20before%20train().%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self._predict_session%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%22Call%20train()%20before%20predict().%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20predict()%20returns%20a%20list%20of%20(n_test%20%C3%97%201)%20arrays%2C%20one%20per%20sample%0A%20%20%20%20%20%20%20%20%20%20%20%20sample_arrays%20%3D%20self._predict_session.predict(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(X_test.astype(np.float64)%2C%20slice(None))%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20np.mean(np.array(sample_arrays)%2C%20axis%3D0).flatten()%0A%0A%20%20%20%20return%20(MacauModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20Path%2C%20np%2C%20pl%2C%20shutil%2C%20subprocess%2C%20sys%2C%20tempfile%2C%20torch)%3A%0A%20%20%20%20import%20os%0A%20%20%20%20%23%20Resolve%20the%20chemprop%20CLI%20from%20the%20same%20venv%20as%20the%20running%20interpreter%0A%20%20%20%20_CHEMPROP_BIN%20%3D%20Path(sys.executable).parent%20%2F%20%22chemprop%22%0A%0A%20%20%20%20%23%20Persistent%20temp%20directories%20%E2%80%94%20one%20per%20model%20type%2C%20reused%20across%20CV%20folds.%0A%20%20%20%20%23%20Using%20a%20fixed%20path%20(not%20TemporaryDirectory)%20so%20the%20folder%20survives%20between%0A%20%20%20%20%23%20train()%20and%20predict()%20calls%20within%20the%20same%20session.%0A%20%20%20%20_CHEMPROP_MODEL_DIR%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_scratch_model%22%0A%20%20%20%20_CHEMELEON_MODEL_DIR%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_chemeleon_model%22%0A%0A%20%20%20%20def%20_get_device()%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Detect%20and%20return%20the%20best%20available%20compute%20device%20for%20PyTorch.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22cuda%22%20if%20an%20NVIDIA%2FAMD%20GPU%20is%20available%2C%20%22mps%22%20if%20running%20on%20Apple%0A%20%20%20%20%20%20%20%20%20%20%20%20Silicon%20with%20Metal%20Performance%20Shaders%2C%20otherwise%20%22cpu%22.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22cuda%22%20%23%20Device%20for%20NVIDIA%20or%20AMD%20GPUs%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20torch.cuda.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%22mps%22%20%23%20Device%20for%20Apple%20Silicon%20(Metal%20Performance%20Shaders)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20torch.backends.mps.is_available()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%20%22cpu%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_write_smiles_csv(%0A%20%20%20%20%20%20%20%20smiles%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20targets%3A%20Optional%5Bnp.ndarray%5D%2C%0A%20%20%20%20%20%20%20%20path%3A%20Path%2C%0A%20%20%20%20%20%20%20%20target_col%3A%20str%2C%0A%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Write%20a%20CSV%20file%20with%20a%20smiles%20column%20and%20an%20optional%20target%20column.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20smiles%3A%20List%20of%20SMILES%20strings.%0A%20%20%20%20%20%20%20%20%20%20%20%20targets%3A%201-D%20array%20of%20target%20values%2C%20or%20None%20for%20inference-only%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20Destination%20file%20path.%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Name%20of%20the%20target%20column.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20targets%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pl.DataFrame(%7B%22smiles%22%3A%20smiles%2C%20target_col%3A%20targets.flatten().tolist()%7D)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pl.DataFrame(%7B%22smiles%22%3A%20smiles%7D)%0A%20%20%20%20%20%20%20%20df.write_csv(path)%0A%0A%20%20%20%20%23%20Log%20file%20for%20all%20chemprop%20CLI%20calls%20%E2%80%94%20persisted%20in%20project%20logs%2F%20folder.%0A%20%20%20%20_CHEMPROP_LOG%20%3D%20Path(%22..%2Flogs%2Fchemprop_cli.log%22)%0A%20%20%20%20_CHEMPROP_LOG.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20def%20_run_chemprop_cli(args%3A%20list%5Bstr%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Run%20the%20chemprop%20CLI%20as%20a%20subprocess%2C%20redirecting%20all%20output%20to%20a%20log%20file.%0A%0A%20%20%20%20%20%20%20%20stdout%20and%20stderr%20are%20appended%20to%20_CHEMPROP_LOG%20so%20the%20notebook%20stays%0A%20%20%20%20%20%20%20%20quiet.%20On%20failure%20the%20tail%20of%20the%20log%20is%20printed%20to%20help%20diagnose%20the%20error.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20args%3A%20Argument%20list%20passed%20after%20the%20%60chemprop%60%20binary.%0A%0A%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20RuntimeError%3A%20If%20the%20process%20exits%20with%20a%20non-zero%20return%20code.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20cmd%20%3D%20%5Bstr(_CHEMPROP_BIN)%5D%20%2B%20args%0A%20%20%20%20%20%20%20%20%23%20Pass%20PYTORCH_MPS_HIGH_WATERMARK_RATIO%3D0.0%20to%20remove%20MPS%20allocator%20ceiling%2C%0A%20%20%20%20%20%20%20%20%23%20preventing%20macOS%20memory%20pressure%20stalls%20between%20folds%20on%20Apple%20Silicon.%0A%20%20%20%20%20%20%20%20_env%20%3D%20%7B**os.environ%2C%20%22PYTORCH_MPS_HIGH_WATERMARK_RATIO%22%3A%20%220.0%22%7D%0A%20%20%20%20%20%20%20%20with%20open(_CHEMPROP_LOG%2C%20%22a%22)%20as%20_log%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_log.write(f%22%5Cn%7B'%3D'*60%7D%5CnCMD%3A%20%7B'%20'.join(cmd)%7D%5Cn%7B'%3D'*60%7D%5Cn%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20result%20%3D%20subprocess.run(cmd%2C%20stdout%3D_log%2C%20stderr%3D_log%2C%20text%3DTrue%2C%20env%3D_env)%0A%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Print%20only%20the%20last%2030%20lines%20of%20the%20log%20to%20surface%20the%20error%0A%20%20%20%20%20%20%20%20%20%20%20%20lines%20%3D%20_CHEMPROP_LOG.read_text().splitlines()%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22%5Cn%22.join(lines%5B-30%3A%5D))%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22chemprop%20CLI%20failed%20(exit%20%7Bresult.returncode%7D).%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Full%20log%3A%20%7B_CHEMPROP_LOG%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20class%20ChempropModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Chemprop%20D-MPNN%20trained%20from%20scratch%20via%20the%20chemprop%20CLI.%0A%0A%20%20%20%20%20%20%20%20train()%20and%20predict()%20shell%20out%20to%20%60chemprop%20train%60%20%2F%20%60chemprop%20predict%60%0A%20%20%20%20%20%20%20%20rather%20than%20using%20the%20Python%20API%2C%20avoiding%20MPS%20memory%20issues%20when%20running%0A%20%20%20%20%20%20%20%20many%20CV%20folds%20inside%20a%20notebook%20kernel.%0A%0A%20%20%20%20%20%20%20%20The%20trained%20model%20is%20written%20to%20a%20fixed%20temporary%20directory%0A%20%20%20%20%20%20%20%20(%2Ftmp%2Fchemprop_scratch_model)%20which%20is%20overwritten%20on%20each%20train()%20call%0A%20%20%20%20%20%20%20%20so%20no%20disk%20space%20accumulates%20across%20folds.%0A%0A%20%20%20%20%20%20%20%20%23%23%20Transfer%20learning%20workflow%0A%0A%20%20%20%20%20%20%20%20Call%20pretrain()%20once%20on%20an%20auxiliary%20dataset%20before%20the%20CV%20loop%2C%20then%0A%20%20%20%20%20%20%20%20call%20train()%20as%20normal.%20%20train()%20detects%20the%20saved%20pretrain%20checkpoint%0A%20%20%20%20%20%20%20%20and%20passes%20it%20to%20%60chemprop%20train%20--checkpoint%60%2C%20initialising%20the%0A%20%20%20%20%20%20%20%20encoder%20weights%20from%20the%20pretraining%20run.%0A%0A%20%20%20%20%20%20%20%20Optionally%20set%20freeze_encoder%3DTrue%20to%20lock%20the%20message-passing%20weights%0A%20%20%20%20%20%20%20%20during%20fine-tuning%2C%20updating%20only%20the%20FFN%20head.%0A%0A%20%20%20%20%20%20%20%20Example%3A%3A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20ChempropModel(pred_type%3D%22regression%22%2C%20freeze_encoder%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20pretrain%20once%20on%20a%20large%20auxiliary%20dataset%0A%20%20%20%20%20%20%20%20%20%20%20%20model.pretrain(X_aux%2C%20y_aux%2C%20X_val_aux%2C%20y_val_aux%2C%20target_col%3D%22pKi%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20fine-tune%20(with%20the%20encoder%20frozen)%20on%20the%20target%20dataset%0A%20%20%20%20%20%20%20%20%20%20%20%20model.train(X_train%2C%20y_train%2C%20X_val%2C%20y_val%2C%20target_col%3D%22pXC50%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20model.predict(X_test)%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Path%20%3D%20_CHEMPROP_MODEL_DIR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20freeze_encoder%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20message_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20depth%3A%20int%20%3D%203%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20int%20%3D%2064%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20float%20%3D%201e-3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22regression%22%20or%20%22classification%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Directory%20where%20fine-tuning%20checkpoints%20are%20written.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Cleared%20before%20every%20train()%20call.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3A%20Directory%20where%20pretraining%20checkpoints%20are%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20written%20and%20persisted.%20%20Defaults%20to%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2Ftmp%2Fchemprop_pretrain_model%20when%20None.%20%20Not%20cleared%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20between%20CV%20folds%20so%20the%20same%20pretrained%20encoder%20can%20be%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reused%20across%20all%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20freeze_encoder%3A%20When%20True%2C%20the%20message-passing%20encoder%20loaded%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20from%20the%20pretrain%20checkpoint%20is%20frozen%20during%20fine-tuning%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--freeze-encoder).%20%20Ignored%20if%20no%20pretrain%20checkpoint%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20exists.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20Maximum%20training%20epochs%20for%20train().%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_epochs%3A%20Maximum%20training%20epochs%20for%20pretrain().%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message_hidden_dim%3A%20Hidden%20dimension%20of%20the%20MPNN%20message-passing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20layers%20(--message-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20depth%3A%20Number%20of%20message-passing%20steps%20(--depth).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20Dropout%20probability%20applied%20after%20each%20message-passing%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20and%20FFN%20layer%20(--dropout).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20Hidden%20dimension%20of%20the%20feed-forward%20network%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--ffn-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20Number%20of%20layers%20in%20the%20feed-forward%20network%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--ffn-num-layers).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20Mini-batch%20size%20for%20training%20(--batch-size).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20Initial%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--init-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20Peak%20learning%20rate%20for%20the%20one-cycle%20scheduler%20(--max-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20Final%20learning%20rate%20for%20the%20one-cycle%20scheduler%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--final-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%20%20%20%20%20%20%20%20%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir%20%20%20%20%20%20%20%20%20%20%3D%20model_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_dir%20%20%20%20%20%20%20%3D%20pretrain_dir%20or%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_pretrain_model%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.freeze_encoder%20%20%20%20%20%3D%20freeze_encoder%0A%20%20%20%20%20%20%20%20%20%20%20%20self.epochs%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_epochs%20%20%20%20%3D%20pretrain_epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.message_hidden_dim%20%3D%20message_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.depth%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20depth%0A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%20%20%20%20%20%20%20%20%20%20%20%20%3D%20dropout%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_hidden_dim%20%20%20%20%20%3D%20ffn_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_num_layers%20%20%20%20%20%3D%20ffn_num_layers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.batch_size%20%20%20%20%20%20%20%20%20%3D%20batch_size%0A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%20%20%20%20%20%20%20%20%20%20%20%20%3D%20init_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20max_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.final_lr%20%20%20%20%20%20%20%20%20%20%20%3D%20final_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%3A%20Optional%5Bstr%5D%20%3D%20None%20%20%23%20set%20during%20train()%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20internal%20helpers%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20%20%20%20%20def%20_base_train_args(self%2C%20task_type%3A%20str%2C%20target_col%3A%20str)%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22Return%20CLI%20args%20shared%20between%20pretrain()%20and%20train().%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--smiles-columns%22%2C%20%22smiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--target-columns%22%2C%20target_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--task-type%22%2C%20task_type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--accelerator%22%2C%20_get_device()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--message-hidden-dim%22%2C%20str(self.message_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--depth%22%2C%20str(self.depth)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--dropout%22%2C%20str(self.dropout)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-hidden-dim%22%2C%20str(self.ffn_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-num-layers%22%2C%20str(self.ffn_num_layers)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--batch-size%22%2C%20str(self.batch_size)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--init-lr%22%2C%20str(self.init_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--max-lr%22%2C%20str(self.max_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--final-lr%22%2C%20str(self.final_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20public%20API%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20%20%20%20%20def%20pretrain(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22pretrain_target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Pretrain%20the%20model%20on%20an%20auxiliary%20dataset.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Trains%20a%20full%20D-MPNN%20from%20scratch%20on%20the%20supplied%20data%20and%20saves%0A%20%20%20%20%20%20%20%20%20%20%20%20the%20checkpoint%20to%20pretrain_dir.%20%20Call%20this%20once%20before%20the%20CV%20loop%0A%20%20%20%20%20%20%20%20%20%20%20%20so%20every%20subsequent%20train()%20call%20can%20warm-start%20from%20the%20same%0A%20%20%20%20%20%20%20%20%20%20%20%20encoder%20weights.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20The%20pretrain_dir%20is%20**not**%20cleared%20between%20calls%2C%20so%20a%20second%0A%20%20%20%20%20%20%20%20%20%20%20%20call%20to%20pretrain()%20will%20overwrite%20the%20previous%20checkpoint.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20the%20auxiliary%20training%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Auxiliary%20training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20auxiliary%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Auxiliary%20validation%20targets.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Should%20differ%20from%20the%20fine-tuning%20target_col%20to%20avoid%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20confusion%20in%20log%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemprop_pretrain_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemprop_pretrain_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pretrain_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.pretrain_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22classification%22%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*self._base_train_args(task_type%2C%20target_col)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.pretrain_epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.pretrain_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20train(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Train%20(or%20fine-tune)%20the%20model%20on%20the%20target%20dataset.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20If%20pretrain()%20was%20called%20beforehand%20and%20the%20checkpoint%20exists%20at%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%2Fmodel_0%2Fbest.pt%2C%20the%20encoder%20weights%20are%20loaded%20via%0A%20%20%20%20%20%20%20%20%20%20%20%20%60--checkpoint%60.%20%20When%20freeze_encoder%3DTrue%20the%20message-passing%0A%20%20%20%20%20%20%20%20%20%20%20%20layers%20are%20also%20frozen%20so%20only%20the%20FFN%20head%20is%20updated.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Writes%20temporary%20CSV%20files%20for%20train%20and%20val%20sets%2C%20runs%20the%20CLI%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20then%20removes%20the%20CSVs.%20The%20model_dir%20is%20cleared%20before%20each%20run.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20training.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Validation%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%20%3D%20target_col%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemprop_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemprop_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Remove%20stale%20fine-tuning%20checkpoints%20so%20the%20CLI%20starts%20fresh%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.model_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.model_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22classification%22%0A%20%20%20%20%20%20%20%20%20%20%20%20args%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*self._base_train_args(task_type%2C%20target_col)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.model_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Warm-start%20from%20pretrain%20checkpoint%20when%20it%20exists%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_ckpt%20%3D%20self.pretrain_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pretrain_ckpt.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20args%20%2B%3D%20%5B%22--checkpoint%22%2C%20str(pretrain_ckpt)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20self.freeze_encoder%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20args.append(%22--freeze-encoder%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(args)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20list%5Bstr%5D)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20inference%20by%20calling%20%60chemprop%20predict%60%20via%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Writes%20a%20temporary%20SMILES%20CSV%2C%20runs%20the%20CLI%2C%20reads%20the%20output%20CSV%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20then%20removes%20both%20temporary%20files.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20SMILES%20strings%20to%20predict.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20numpy%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv%20%3D%20tmp%20%20%2F%20%22chemprop_test.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv%20%3D%20tmp%20%20%2F%20%22chemprop_preds.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20The%20best.pt%20written%20by%20%60chemprop%20train%60%20into%20model_dir%2Fmodel_0%2F%0A%20%20%20%20%20%20%20%20%20%20%20%20model_pt%20%3D%20self.model_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_test%2C%20None%2C%20test_csv%2C%20self.target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predict%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--test-path%22%2C%20%20str(test_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--model-path%22%2C%20str(model_pt)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--preds-path%22%2C%20str(pred_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20pl.read_csv(pred_csv)%5Bself.target_col%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20preds.flatten()%0A%0A%20%20%20%20class%20ChempropChemeleonModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Chemprop%20D-MPNN%20fine-tuned%20from%20the%20CheMeleon%20pretrained%20backbone%20via%20the%20CLI.%0A%0A%20%20%20%20%20%20%20%20Identical%20interface%20to%20ChempropModel%20but%20passes%20%60--from-foundation%20CHEMELEON%60%0A%20%20%20%20%20%20%20%20to%20%60chemprop%20train%60%20so%20the%20MPNN%20encoder%20is%20always%20warm-started%20from%20the%0A%20%20%20%20%20%20%20%20CheMeleon%20weights.%20%20The%20CLI%20downloads%20and%20caches%20those%20weights%0A%20%20%20%20%20%20%20%20automatically%20at%20~%2F.chemprop%2Fchemeleon_mp.pt%20on%20the%20first%20call.%0A%0A%20%20%20%20%20%20%20%20%23%23%20Transfer%20learning%20workflow%0A%0A%20%20%20%20%20%20%20%20Call%20pretrain()%20once%20on%20an%20auxiliary%20dataset%20before%20the%20CV%20loop%2C%20then%0A%20%20%20%20%20%20%20%20call%20train()%20as%20normal.%20%20pretrain()%20itself%20starts%20from%20the%20CheMeleon%0A%20%20%20%20%20%20%20%20backbone%3B%20train()%20then%20warm-starts%20from%20the%20pretrain%20checkpoint%2C%0A%20%20%20%20%20%20%20%20giving%20a%20three-stage%20initialisation%20chain%3A%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20CheMeleon%20%20%E2%86%92%20%20pretrain%20(auxiliary%20data)%20%20%E2%86%92%20%20train%20(target%20data)%0A%0A%20%20%20%20%20%20%20%20Optionally%20set%20freeze_encoder%3DTrue%20to%20lock%20the%20message-passing%20weights%0A%20%20%20%20%20%20%20%20during%20fine-tuning%2C%20updating%20only%20the%20FFN%20head.%0A%0A%20%20%20%20%20%20%20%20Reference%3A%20https%3A%2F%2Fgithub.com%2FJacksonBurns%2Fchemeleon%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Path%20%3D%20_CHEMELEON_MODEL_DIR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20freeze_encoder%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20int%20%3D%20900%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20int%20%3D%2064%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20float%20%3D%201e-3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22regression%22%20or%20%22classification%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Directory%20where%20fine-tuning%20checkpoints%20are%20written.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Distinct%20from%20ChempropModel's%20default%20to%20avoid%20collisions.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3A%20Directory%20for%20pretraining%20checkpoints.%20%20Defaults%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20to%20%2Ftmp%2Fchemeleon_pretrain_model%20when%20None.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20freeze_encoder%3A%20When%20True%2C%20the%20message-passing%20encoder%20from%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain%20checkpoint%20is%20frozen%20during%20fine-tuning.%20%20Ignored%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20no%20pretrain%20checkpoint%20exists.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20Maximum%20training%20epochs%20for%20train().%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_epochs%3A%20Maximum%20training%20epochs%20for%20pretrain().%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20Dropout%20probability%20applied%20after%20each%20FFN%20layer.%20Note%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20that%20message-passing%20architecture%20is%20fixed%20by%20CheMeleon%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(2048%20d_h%2C%20depth%206)%20and%20cannot%20be%20changed%20during%20fine-tuning.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20Hidden%20dimension%20of%20the%20task-specific%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20feed-forward%20head%20(--ffn-hidden-dim).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20Number%20of%20layers%20in%20the%20feed-forward%20head%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(--ffn-num-layers).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20Mini-batch%20size%20(--batch-size).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20Initial%20learning%20rate%20(--init-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20Peak%20learning%20rate%20(--max-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20Final%20learning%20rate%20(--final-lr).%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%20%20%20%20%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir%20%20%20%20%20%20%3D%20model_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_dir%20%20%20%3D%20pretrain_dir%20or%20Path(tempfile.gettempdir())%20%2F%20%22chemeleon_pretrain_model%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.freeze_encoder%20%3D%20freeze_encoder%0A%20%20%20%20%20%20%20%20%20%20%20%20self.epochs%20%20%20%20%20%20%20%20%20%3D%20epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_epochs%20%3D%20pretrain_epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%20%20%20%20%20%20%20%20%3D%20dropout%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_hidden_dim%20%3D%20ffn_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_num_layers%20%3D%20ffn_num_layers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.batch_size%20%20%20%20%20%3D%20batch_size%0A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%20%20%20%20%20%20%20%20%3D%20init_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%20%20%20%20%20%20%20%20%20%3D%20max_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.final_lr%20%20%20%20%20%20%20%3D%20final_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%3A%20Optional%5Bstr%5D%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20internal%20helpers%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20%20%20%20%20def%20_base_train_args(self%2C%20task_type%3A%20str%2C%20target_col%3A%20str)%20-%3E%20list%5Bstr%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22Return%20CLI%20args%20shared%20between%20pretrain()%20and%20train().%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--smiles-columns%22%2C%20%22smiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--target-columns%22%2C%20target_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--task-type%22%2C%20task_type%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--accelerator%22%2C%20_get_device()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--dropout%22%2C%20str(self.dropout)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-hidden-dim%22%2C%20str(self.ffn_hidden_dim)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--ffn-num-layers%22%2C%20str(self.ffn_num_layers)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--batch-size%22%2C%20str(self.batch_size)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--init-lr%22%2C%20str(self.init_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--max-lr%22%2C%20str(self.max_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--final-lr%22%2C%20str(self.final_lr)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20public%20API%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20%20%20%20%20def%20pretrain(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22pretrain_target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Pretrain%20on%20an%20auxiliary%20dataset%2C%20starting%20from%20the%20CheMeleon%20backbone.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Runs%20%60chemprop%20train%20--from-foundation%20CHEMELEON%60%20on%20the%20auxiliary%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20and%20saves%20the%20checkpoint%20to%20pretrain_dir.%20%20The%20resulting%0A%20%20%20%20%20%20%20%20%20%20%20%20checkpoint%20encodes%20both%20CheMeleon%20priors%20and%20auxiliary-task%0A%20%20%20%20%20%20%20%20%20%20%20%20knowledge%2C%20and%20is%20used%20to%20warm-start%20subsequent%20train()%20calls.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20the%20auxiliary%20training%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Auxiliary%20training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20auxiliary%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Auxiliary%20validation%20targets.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemeleon_pretrain_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemeleon_pretrain_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pretrain_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.pretrain_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22classification%22%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*self._base_train_args(task_type%2C%20target_col)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.pretrain_epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--from-foundation%22%2C%20%22CHEMELEON%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.pretrain_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20train(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fine-tune%20on%20the%20target%20dataset.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20When%20a%20pretrain%20checkpoint%20exists%20at%20pretrain_dir%2Fmodel_0%2Fbest.pt%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20the%20encoder%20weights%20are%20loaded%20via%20%60--checkpoint%60%2C%20giving%20a%0A%20%20%20%20%20%20%20%20%20%20%20%20three-stage%20chain%3A%20CheMeleon%20%E2%86%92%20pretrain%20%E2%86%92%20fine-tune.%0A%20%20%20%20%20%20%20%20%20%20%20%20Without%20a%20pretrain%20checkpoint%20the%20model%20falls%20back%20to%0A%20%20%20%20%20%20%20%20%20%20%20%20%60--from-foundation%20CHEMELEON%60%20(standard%20two-stage%20fine-tuning).%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20SMILES%20strings%20for%20training.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20Training%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20SMILES%20strings%20for%20validation%20(early%20stopping).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20Validation%20targets%2C%20shape%20(n%2C)%20or%20(n%2C%201).%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20Column%20name%20used%20in%20the%20temporary%20CSV%20files.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%20%3D%20target_col%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv%20%3D%20tmp%20%2F%20%22chemeleon_train.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv%20%20%20%3D%20tmp%20%2F%20%22chemeleon_val.csv%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_train%2C%20y_train%2C%20train_csv%2C%20target_col)%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_val%2C%20%20%20y_val%2C%20%20%20val_csv%2C%20%20%20target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.model_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.model_dir)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20task_type%20%3D%20%22regression%22%20if%20self.pred_type%20%3D%3D%20%22regression%22%20else%20%22classification%22%0A%20%20%20%20%20%20%20%20%20%20%20%20args%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--data-path%22%2C%20str(train_csv)%2C%20str(val_csv)%2C%20str(val_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*self._base_train_args(task_type%2C%20target_col)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--epochs%22%2C%20str(self.epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--save-dir%22%2C%20str(self.model_dir)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Three-stage%3A%20load%20pretrain%20checkpoint%20when%20available%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_ckpt%20%3D%20self.pretrain_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pretrain_ckpt.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20args%20%2B%3D%20%5B%22--checkpoint%22%2C%20str(pretrain_ckpt)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20self.freeze_encoder%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20args.append(%22--freeze-encoder%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Fall%20back%20to%20standard%20two-stage%20CheMeleon%20fine-tuning%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20args%20%2B%3D%20%5B%22--from-foundation%22%2C%20%22CHEMELEON%22%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(args)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20train_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20val_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20list%5Bstr%5D)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20inference%20by%20calling%20%60chemprop%20predict%60%20via%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20SMILES%20strings%20to%20predict.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20numpy%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv%20%3D%20tmp%20%20%2F%20%22chemeleon_test.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv%20%3D%20tmp%20%20%2F%20%22chemeleon_preds.csv%22%0A%20%20%20%20%20%20%20%20%20%20%20%20model_pt%20%3D%20self.model_dir%20%2F%20%22model_0%22%20%2F%20%22best.pt%22%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_write_smiles_csv(X_test%2C%20None%2C%20test_csv%2C%20self.target_col)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_chemprop_cli(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22predict%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--test-path%22%2C%20%20str(test_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--model-path%22%2C%20str(model_pt)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22--preds-path%22%2C%20str(pred_csv)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20pl.read_csv(pred_csv)%5Bself.target_col%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20test_csv.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_csv.unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20preds.flatten()%0A%0A%20%20%20%20return%20ChempropChemeleonModel%2C%20ChempropModel%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20json%2C%20np%2C%20subprocess%2C%20sys%2C%20tempfile)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20ChempropAPIModel%20%E2%80%94%20Python-API%20subprocess%20variant%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Identical%20interface%20to%20ChempropModel%20but%20runs%20chemprop%20via%20its%20Python%20API%0A%20%20%20%20%23%20(Lightning%20Trainer)%20instead%20of%20the%20CLI.%20%20The%20key%20benefit%20is%20explicit%20MPS%0A%20%20%20%20%23%20memory%20management%3A%20the%20subprocess%20calls%20torch.mps.empty_cache()%20and%0A%20%20%20%20%23%20torch.mps.synchronize()%20before%20exit%2C%20returning%20a%20clean%20Metal%20pool%20to%20macOS%0A%20%20%20%20%23%20immediately%20rather%20than%20waiting%20for%20the%20background%20Metal%20GC.%20%20This%0A%20%20%20%20%23%20eliminates%20the%20multi-minute%20inter-fold%20stalls%20seen%20with%20the%20CLI%20approach.%0A%20%20%20%20%23%0A%20%20%20%20%23%20Two%20scripts%20are%20written%20to%20%2Ftmp%20at%20cell%20init%20and%20reused%20across%20all%20calls%3A%0A%20%20%20%20%23%20%20%20_TRAIN_SCRIPT%20%20%E2%80%94%20train%20on%20(X_train_smiles%2C%20y_train%2C%20X_val_smiles%2C%20y_val)%2C%0A%20%20%20%20%23%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20save%20best%20checkpoint%2C%20write%20predictions%20on%20X_test.%0A%20%20%20%20%23%20%20%20_PREDICT_SCRIPT%20%E2%80%94%20load%20checkpoint%2C%20write%20predictions%20on%20X_test.%0A%20%20%20%20%23%20Data%20is%20exchanged%20via%20JSON%20(SMILES%20lists)%20and%20.npy%20files%20(targets%20%2F%20preds).%0A%0A%20%20%20%20_TRAIN_SCRIPT_LINES%20%3D%20%22%22%22%0A%20%20%20%20import%20os%2C%20sys%2C%20json%2C%20numpy%20as%20np%2C%20tempfile%2C%20torch%0A%20%20%20%20from%20datetime%20import%20datetime%2C%20timezone%0A%20%20%20%20os.environ%5B%22PYTORCH_MPS_HIGH_WATERMARK_RATIO%22%5D%20%3D%20%220.0%22%0A%20%20%20%20os.environ%5B%22KMP_DUPLICATE_LIB_OK%22%5D%20%3D%20%22TRUE%22%0A%0A%20%20%20%20_API_LOG%20%3D%20os.path.join(%22logs%22%2C%20%22chemprop_api.log%22)%0A%20%20%20%20os.makedirs(%22logs%22%2C%20exist_ok%3DTrue)%0A%20%20%20%20def%20_log(msg)%3A%0A%20%20%20%20%20%20%20%20ts%20%3D%20datetime.now(timezone.utc).astimezone().isoformat(timespec%3D%22seconds%22)%0A%20%20%20%20%20%20%20%20line%20%3D%20f%22%7Bts%7D%20-%20%7Bmsg%7D%5C%5Cn%22%0A%20%20%20%20%20%20%20%20with%20open(_API_LOG%2C%20%22a%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_f.write(line)%0A%0A%20%20%20%20_log(%22train%20START%22)%0A%20%20%20%20import%20lightning%20as%20L%0A%20%20%20%20from%20lightning.pytorch.callbacks%20import%20ModelCheckpoint%0A%20%20%20%20from%20lightning.pytorch.callbacks.early_stopping%20import%20EarlyStopping%0A%20%20%20%20from%20chemprop%20import%20data%2C%20models%2C%20nn%20as%20chemnn%0A%20%20%20%20from%20chemprop.data%20import%20build_dataloader%0A%0A%20%20%20%20args%20%20%20%20%20%20%3D%20json.loads(sys.argv%5B1%5D)%0A%20%20%20%20smi_tr%20%20%20%20%3D%20args%5B%22smi_tr%22%5D%0A%20%20%20%20smi_val%20%20%20%3D%20args%5B%22smi_val%22%5D%0A%20%20%20%20smi_test%20%20%3D%20args%5B%22smi_test%22%5D%0A%20%20%20%20y_tr%20%20%20%20%20%20%3D%20np.load(args%5B%22y_tr%22%5D)%0A%20%20%20%20y_val%20%20%20%20%20%3D%20np.load(args%5B%22y_val%22%5D)%0A%20%20%20%20out_preds%20%3D%20args%5B%22out_preds%22%5D%0A%20%20%20%20ckpt_path%20%3D%20args%5B%22ckpt_path%22%5D%0A%20%20%20%20params%20%20%20%20%3D%20args%5B%22params%22%5D%0A%0A%20%20%20%20def%20build_ds(smiles%2C%20targets%3DNone)%3A%0A%20%20%20%20%20%20%20%20if%20targets%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20data.MoleculeDataset(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data.MoleculeDatapoint.from_smi(s%2C%20%5Bfloat(v)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20s%2C%20v%20in%20zip(smiles%2C%20targets)%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20return%20data.MoleculeDataset(%5Bdata.MoleculeDatapoint.from_smi(s)%20for%20s%20in%20smiles%5D)%0A%0A%20%20%20%20ds_tr%20%20%20%3D%20build_ds(smi_tr%2C%20%20%20y_tr)%0A%20%20%20%20ds_val%20%20%3D%20build_ds(smi_val%2C%20%20y_val)%0A%20%20%20%20ds_test%20%3D%20build_ds(smi_test)%0A%20%20%20%20batch_size%20%3D%20params.get(%22batch_size%22%2C%2064)%0A%20%20%20%20dl_tr%20%20%20%3D%20build_dataloader(ds_tr%2C%20%20%20shuffle%3DTrue%2C%20%20num_workers%3D0%2C%20batch_size%3Dbatch_size)%0A%20%20%20%20dl_val%20%20%3D%20build_dataloader(ds_val%2C%20%20shuffle%3DFalse%2C%20num_workers%3D0%2C%20batch_size%3Dbatch_size)%0A%20%20%20%20dl_test%20%3D%20build_dataloader(ds_test%2C%20shuffle%3DFalse%2C%20num_workers%3D0%2C%20batch_size%3Dbatch_size)%0A%0A%20%20%20%20pretrain_ckpt%20%3D%20params.get(%22pretrain_ckpt%22)%0A%20%20%20%20if%20pretrain_ckpt%20and%20os.path.exists(pretrain_ckpt)%3A%0A%20%20%20%20%20%20%20%20model%20%3D%20models.MPNN.load_from_file(pretrain_ckpt%2C%20map_location%3D%22cpu%22)%0A%20%20%20%20%20%20%20%20model.init_lr%20%20%3D%20params.get(%22init_lr%22%2C%20%201e-4)%0A%20%20%20%20%20%20%20%20model.max_lr%20%20%20%3D%20params.get(%22max_lr%22%2C%20%20%201e-3)%0A%20%20%20%20%20%20%20%20model.final_lr%20%3D%20params.get(%22final_lr%22%2C%201e-4)%0A%20%20%20%20%20%20%20%20model.apply(lambda%20m%3A%20setattr(m%2C%20%22p%22%2C%20params.get(%22dropout%22%2C%200.0))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance(m%2C%20torch.nn.Dropout)%20else%20None)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20mp%20%20%3D%20chemnn.BondMessagePassing(%0A%20%20%20%20%20%20%20%20%20%20%20%20d_h%3Dparams%5B%22message_hidden_dim%22%5D%2C%20depth%3Dparams%5B%22depth%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3Dparams%5B%22dropout%22%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ffn%20%3D%20chemnn.RegressionFFN(%0A%20%20%20%20%20%20%20%20%20%20%20%20input_dim%3Dmp.output_dim%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_layers%3Dparams%5B%22ffn_num_layers%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20hidden_dim%3Dparams%5B%22ffn_hidden_dim%22%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20model%20%3D%20models.MPNN(%0A%20%20%20%20%20%20%20%20%20%20%20%20mp%2C%20chemnn.MeanAggregation()%2C%20ffn%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3Dparams.get(%22init_lr%22%2C%201e-4)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3Dparams.get(%22max_lr%22%2C%201e-3)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3Dparams.get(%22final_lr%22%2C%201e-4)%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20ckpt_dir%20%3D%20os.path.dirname(ckpt_path)%0A%20%20%20%20os.makedirs(ckpt_dir%2C%20exist_ok%3DTrue)%0A%20%20%20%20%23%20Strip%20any%20extension%20so%20ModelCheckpoint%20saves%20as%20%22best.ckpt%22%20(it%20appends%20.ckpt%20itself)%0A%20%20%20%20ckpt_stem%20%3D%20os.path.splitext(os.path.basename(ckpt_path))%5B0%5D%0A%20%20%20%20ckpt_cb%20%3D%20ModelCheckpoint(dirpath%3Dckpt_dir%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20filename%3Dckpt_stem%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20monitor%3D%22val_loss%22%2C%20save_top_k%3D1%2C%20mode%3D%22min%22)%0A%20%20%20%20es_cb%20%20%20%3D%20EarlyStopping(monitor%3D%22val_loss%22%2C%20patience%3D10%2C%20mode%3D%22min%22)%0A%0A%20%20%20%20trainer%20%3D%20L.Trainer(%0A%20%20%20%20%20%20%20%20max_epochs%3Dparams%5B%22epochs%22%5D%2C%0A%20%20%20%20%20%20%20%20enable_progress_bar%3DFalse%2C%20enable_model_summary%3DFalse%2C%20logger%3DFalse%2C%0A%20%20%20%20%20%20%20%20accelerator%3D%22auto%22%2C%0A%20%20%20%20%20%20%20%20callbacks%3D%5Bckpt_cb%2C%20es_cb%5D%2C%0A%20%20%20%20)%0A%20%20%20%20trainer.fit(model%2C%20dl_tr%2C%20dl_val)%0A%20%20%20%20best%20%3D%20models.MPNN.load_from_file(ckpt_cb.best_model_path%2C%20map_location%3D%22cpu%22)%0A%20%20%20%20best.eval()%0A%20%20%20%20preds%20%3D%20trainer.predict(best%2C%20dl_test)%0A%20%20%20%20np.save(out_preds%2C%20np.concatenate(%5Bp.numpy()%20for%20p%20in%20preds%5D).flatten())%0A%0A%20%20%20%20if%20torch.backends.mps.is_available()%3A%0A%20%20%20%20%20%20%20%20torch.mps.synchronize()%0A%20%20%20%20%20%20%20%20torch.mps.empty_cache()%0A%20%20%20%20_log(%22train%20END%20(MPS%20cache%20flushed)%22)%0A%20%20%20%20%22%22%22.strip()%0A%0A%20%20%20%20_PREDICT_SCRIPT_LINES%20%3D%20%22%22%22%0A%20%20%20%20import%20os%2C%20sys%2C%20json%2C%20tempfile%2C%20numpy%20as%20np%2C%20torch%0A%20%20%20%20os.environ%5B%22PYTORCH_MPS_HIGH_WATERMARK_RATIO%22%5D%20%3D%20%220.0%22%0A%20%20%20%20os.environ%5B%22KMP_DUPLICATE_LIB_OK%22%5D%20%3D%20%22TRUE%22%0A%0A%20%20%20%20_API_LOG%20%3D%20os.path.join(%22logs%22%2C%20%22chemprop_api.log%22)%0A%20%20%20%20os.makedirs(%22logs%22%2C%20exist_ok%3DTrue)%0A%20%20%20%20def%20_log(msg)%3A%0A%20%20%20%20%20%20%20%20from%20datetime%20import%20datetime%2C%20timezone%0A%20%20%20%20%20%20%20%20ts%20%3D%20datetime.now(timezone.utc).astimezone().isoformat(timespec%3D%22seconds%22)%0A%20%20%20%20%20%20%20%20with%20open(_API_LOG%2C%20%22a%22)%20as%20_f%3A%20_f.write(f%22%7Bts%7D%20-%20%7Bmsg%7D%5C%5Cn%22)%0A%0A%20%20%20%20_log(%22predict%20START%22)%0A%20%20%20%20import%20lightning%20as%20L%0A%20%20%20%20from%20chemprop%20import%20data%2C%20models%0A%20%20%20%20from%20chemprop.data%20import%20build_dataloader%0A%0A%20%20%20%20args%20%20%20%20%20%20%3D%20json.loads(sys.argv%5B1%5D)%0A%20%20%20%20smi_test%20%20%3D%20args%5B%22smi_test%22%5D%0A%20%20%20%20ckpt_path%20%3D%20args%5B%22ckpt_path%22%5D%0A%20%20%20%20out_preds%20%3D%20args%5B%22out_preds%22%5D%0A%0A%20%20%20%20ds_test%20%3D%20data.MoleculeDataset(%5Bdata.MoleculeDatapoint.from_smi(s)%20for%20s%20in%20smi_test%5D)%0A%20%20%20%20dl_test%20%3D%20build_dataloader(ds_test%2C%20shuffle%3DFalse%2C%20num_workers%3D0)%0A%20%20%20%20model%20%20%20%3D%20models.MPNN.load_from_file(ckpt_path%2C%20map_location%3D%22cpu%22)%0A%20%20%20%20model.eval()%0A%0A%20%20%20%20trainer%20%3D%20L.Trainer(enable_progress_bar%3DFalse%2C%20enable_model_summary%3DFalse%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20logger%3DFalse%2C%20accelerator%3D%22auto%22)%0A%20%20%20%20preds%20%3D%20trainer.predict(model%2C%20dl_test)%0A%20%20%20%20np.save(out_preds%2C%20np.concatenate(%5Bp.numpy()%20for%20p%20in%20preds%5D).flatten())%0A%0A%20%20%20%20if%20torch.backends.mps.is_available()%3A%0A%20%20%20%20%20%20%20%20torch.mps.synchronize()%0A%20%20%20%20%20%20%20%20torch.mps.empty_cache()%0A%20%20%20%20_log(%22predict%20END%20(MPS%20cache%20flushed)%22)%0A%20%20%20%20%22%22%22.strip()%0A%0A%20%20%20%20import%20textwrap%20as%20_tw%0A%20%20%20%20_API_TRAIN_SCRIPT%20%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_api_train.py%22%0A%20%20%20%20_API_PREDICT_SCRIPT%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_api_predict.py%22%0A%20%20%20%20_API_TRAIN_SCRIPT.write_text(_tw.dedent(_TRAIN_SCRIPT_LINES))%0A%20%20%20%20_API_PREDICT_SCRIPT.write_text(_tw.dedent(_PREDICT_SCRIPT_LINES))%0A%0A%20%20%20%20_API_MODEL_DIR%20%20%20%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_api_model%22%0A%20%20%20%20_API_PRETRAIN_DIR%20%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22chemprop_api_pretrain_model%22%0A%0A%20%20%20%20def%20_run_api_script(script%3A%20Path%2C%20args_dict%3A%20dict)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22Run%20a%20chemprop%20API%20subprocess%2C%20raising%20RuntimeError%20on%20failure.%22%22%22%0A%20%20%20%20%20%20%20%20result%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20str(script)%2C%20json.dumps(args_dict)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20result.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22ChempropAPIModel%20subprocess%20failed%3A%5Cn%7Bresult.stderr%5B-2000%3A%5D%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20class%20ChempropAPIModel%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Chemprop%20D-MPNN%20trained%20via%20the%20Python%20API%20in%20an%20isolated%20subprocess.%0A%0A%20%20%20%20%20%20%20%20Identical%20interface%20to%20ChempropModel%20(train%20%2F%20predict%20%2F%20pretrain)%20but%0A%20%20%20%20%20%20%20%20uses%20the%20Lightning%20Trainer%20directly%20instead%20of%20the%20CLI.%20%20The%20subprocess%0A%20%20%20%20%20%20%20%20calls%20torch.mps.empty_cache()%20%2B%20torch.mps.synchronize()%20before%20exit%2C%0A%20%20%20%20%20%20%20%20returning%20the%20Metal%20pool%20to%20macOS%20immediately%20and%20eliminating%20the%0A%20%20%20%20%20%20%20%20multi-minute%20inter-fold%20MPS%20memory%20stalls%20observed%20with%20the%20CLI%20approach.%0A%0A%20%20%20%20%20%20%20%20Results%20should%20be%20numerically%20equivalent%20to%20ChempropModel%20for%20the%20same%0A%20%20%20%20%20%20%20%20hyperparameters%20and%20seed%20%E2%80%94%20use%20both%20classes%20in%20parallel%20to%20verify%0A%20%20%20%20%20%20%20%20reproducibility%20before%20switching%20production%20runs%20to%20this%20class.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20str%20%3D%20%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20model_dir%3A%20Path%20%3D%20_API_MODEL_DIR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3A%20Path%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20freeze_encoder%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pretrain_epochs%3A%20int%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20message_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20depth%3A%20int%20%3D%203%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%3A%20float%20%3D%200.0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%3A%20int%20%3D%20300%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%3A%20int%20%3D%202%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%3A%20int%20%3D%2064%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20init_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%3A%20float%20%3D%201e-3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20final_lr%3A%20float%20%3D%201e-4%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20pred_type%20not%20in%20(%22regression%22%2C%20%22classification%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22pred_type%20must%20be%20'regression'%20or%20'classification'%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pred_type%20%20%20%20%20%20%20%20%20%20%3D%20pred_type%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir%20%20%20%20%20%20%20%20%20%20%3D%20model_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_dir%20%20%20%20%20%20%20%3D%20pretrain_dir%20or%20_API_PRETRAIN_DIR%0A%20%20%20%20%20%20%20%20%20%20%20%20self.freeze_encoder%20%20%20%20%20%3D%20freeze_encoder%0A%20%20%20%20%20%20%20%20%20%20%20%20self.epochs%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_epochs%20%20%20%20%3D%20pretrain_epochs%0A%20%20%20%20%20%20%20%20%20%20%20%20self.message_hidden_dim%20%3D%20message_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.depth%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20depth%0A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%20%20%20%20%20%20%20%20%20%20%20%20%3D%20dropout%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_hidden_dim%20%20%20%20%20%3D%20ffn_hidden_dim%0A%20%20%20%20%20%20%20%20%20%20%20%20self.ffn_num_layers%20%20%20%20%20%3D%20ffn_num_layers%0A%20%20%20%20%20%20%20%20%20%20%20%20self.batch_size%20%20%20%20%20%20%20%20%20%3D%20batch_size%0A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%20%20%20%20%20%20%20%20%20%20%20%20%3D%20init_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%20%20%20%20%20%20%20%20%20%20%20%20%20%3D%20max_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.final_lr%20%20%20%20%20%20%20%20%20%20%20%3D%20final_lr%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%3A%20str%20%7C%20None%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20def%20_params(self%2C%20epochs%3A%20int)%20-%3E%20dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22Serialisable%20params%20dict%20passed%20to%20subprocess%20scripts.%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22message_hidden_dim%22%3A%20self.message_hidden_dim%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22depth%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.depth%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22dropout%22%3A%20%20%20%20%20%20%20%20%20%20%20%20self.dropout%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ffn_hidden_dim%22%3A%20%20%20%20%20self.ffn_hidden_dim%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ffn_num_layers%22%3A%20%20%20%20%20self.ffn_num_layers%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22batch_size%22%3A%20%20%20%20%20%20%20%20%20self.batch_size%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22init_lr%22%3A%20%20%20%20%20%20%20%20%20%20%20%20self.init_lr%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22max_lr%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20self.max_lr%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22final_lr%22%3A%20%20%20%20%20%20%20%20%20%20%20self.final_lr%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22epochs%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20epochs%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pretrain_ckpt%22%3A%20%20%20%20%20%20str(self.pretrain_dir%20%2F%20%22best.ckpt%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20def%20pretrain(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22pretrain_target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Pretrain%20on%20an%20auxiliary%20dataset%20via%20the%20Python%20API%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Saves%20the%20best%20checkpoint%20to%20pretrain_dir%2Fbest.pt.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20import%20shutil%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.pretrain_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.pretrain_dir)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.pretrain_dir.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20y_tr_f%20%20%3D%20tmp%20%2F%20%22api_pt_ytr.npy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val_f%20%3D%20tmp%20%2F%20%22api_pt_yval.npy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20np.save(str(y_tr_f)%2C%20%20y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20np.save(str(y_val_f)%2C%20y_val)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20params%20%3D%20%7B**self._params(self.pretrain_epochs)%2C%20%22pretrain_ckpt%22%3A%20%22%22%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_api_script(_API_TRAIN_SCRIPT%2C%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_tr%22%3A%20%20%20%20X_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_val%22%3A%20%20%20X_val%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_test%22%3A%20%20X_val%2C%20%20%20%20%20%20%20%20%20%20%23%20dummy%20%E2%80%94%20preds%20not%20used%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_tr%22%3A%20%20%20%20%20%20str(y_tr_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_val%22%3A%20%20%20%20%20str(y_val_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22out_preds%22%3A%20str(tmp%20%2F%20%22api_pt_dummy_preds%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ckpt_path%22%3A%20str(self.pretrain_dir%20%2F%20%22best.pt%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22params%22%3A%20%20%20%20params%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20f%20in%20%5By_tr_f%2C%20y_val_f%2C%20tmp%20%2F%20%22api_pt_dummy_preds.npy%22%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Path(f).unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20train(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_train%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_train%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20X_val%3A%20%20%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val%3A%20%20%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3A%20str%20%3D%20%22target%22%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Fine-tune%20(or%20train%20from%20scratch)%20via%20the%20Python%20API%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20If%20pretrain_dir%2Fbest.pt%20exists%2C%20encoder%20weights%20are%20loaded%20from%20it.%0A%20%20%20%20%20%20%20%20%20%20%20%20Saves%20the%20best%20checkpoint%20to%20model_dir%2Fbest.pt.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20import%20shutil%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.model_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20shutil.rmtree(self.model_dir)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.model_dir.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20self.target_col%20%3D%20target_col%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20y_tr_f%20%20%3D%20tmp%20%2F%20%22api_ytr.npy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20y_val_f%20%3D%20tmp%20%2F%20%22api_yval.npy%22%0A%20%20%20%20%20%20%20%20%20%20%20%20out_f%20%20%20%3D%20tmp%20%2F%20%22api_train_preds%22%0A%20%20%20%20%20%20%20%20%20%20%20%20np.save(str(y_tr_f)%2C%20%20y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20np.save(str(y_val_f)%2C%20y_val)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_api_script(_API_TRAIN_SCRIPT%2C%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_tr%22%3A%20%20%20%20X_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_val%22%3A%20%20%20X_val%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_test%22%3A%20%20X_val%2C%20%20%20%20%20%20%20%20%20%20%23%20dummy%20%E2%80%94%20preds%20not%20used%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_tr%22%3A%20%20%20%20%20%20str(y_tr_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_val%22%3A%20%20%20%20%20str(y_val_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22out_preds%22%3A%20str(out_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ckpt_path%22%3A%20str(self.model_dir%20%2F%20%22best.ckpt%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22params%22%3A%20%20%20%20self._params(self.epochs)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20f%20in%20%5By_tr_f%2C%20y_val_f%2C%20Path(str(out_f)%20%2B%20%22.npy%22)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Path(f).unlink(missing_ok%3DTrue)%0A%0A%20%20%20%20%20%20%20%20def%20predict(self%2C%20X_test%3A%20list%5Bstr%5D)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20Run%20inference%20via%20the%20Python%20API%20subprocess.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20X_test%3A%20SMILES%20strings%20to%20predict.%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201-D%20numpy%20array%20of%20predicted%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20tmp%20%20%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20%20%20%20%20out_f%20%3D%20tmp%20%2F%20%22api_preds%22%0A%20%20%20%20%20%20%20%20%20%20%20%20_run_api_script(_API_PREDICT_SCRIPT%2C%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smi_test%22%3A%20%20X_test%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ckpt_path%22%3A%20str(self.model_dir%20%2F%20%22best.ckpt%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22out_preds%22%3A%20str(out_f)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20np.load(str(out_f)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20Path(str(out_f)%20%2B%20%22.npy%22).unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20preds.flatten()%0A%0A%20%20%20%20return%20(ChempropAPIModel%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20CLI%20vs%20API%20reproducibility%20comparison%0A%0A%20%20%20%20Runs%20both%20%60ChempropModel%60%20(CLI)%20and%20%60ChempropAPIModel%60%20(Python%20API%20subprocess)%0A%20%20%20%20on%20the%20same%205%C3%975%20CV%20splits%20with%20identical%20default%20hyperparameters.%20%20Because%0A%20%20%20%20both%20use%20MPS%20with%20non-deterministic%20GPU%20ops%2C%20predictions%20will%20not%20be%0A%20%20%20%20bit-identical%2C%20but%20the%20Spearman%20%CF%81%20and%20MAE%20should%20be%20statistically%0A%20%20%20%20indistinguishable%20across%20folds.%0A%0A%20%20%20%20Results%20are%20saved%20to%20%60predictions%2F4_api_vs_cli_comparison.csv.gz%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropAPIModel%2C%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20pretrain_dr_train%2C%0A%20%20%20%20rm_tukey_hsd%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20_TARGET_COL%20%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_COMP_PATH_GZ%20%20%3D%20Path(%22..%2Fpredictions%2F4_api_vs_cli_comparison.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%0A%0A%20%20%20%20_EXPECTED_COMP%20%3D%20%7B%22api%22%2C%20%22cli%22%7D%0A%20%20%20%20_is_comp_complete%20%3D%20(%0A%20%20%20%20%20%20%20%20_COMP_PATH_GZ.exists()%0A%20%20%20%20%20%20%20%20and%20set(pl.read_csv(_COMP_PATH_GZ)%5B%22model%22%5D.unique().to_list())%20%3E%3D%20_EXPECTED_COMP%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_comp_complete%3A%0A%20%20%20%20%20%20%20%20print(f%22Comparison%20results%20found%20%E2%80%94%20loading.%22)%0A%20%20%20%20%20%20%20%20_comp_df%20%3D%20pl.read_csv(_COMP_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_all_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%0A%20%20%20%20%20%20%20%20for%20_model_key%2C%20_ModelClass%20in%20%5B(%22api%22%2C%20ChempropAPIModel)%2C%20(%22cli%22%2C%20ChempropModel)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_pbar%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dr_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_n_folds%2C%20desc%3Df%22CV%20%7B_model_key%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20_ModelClass(pred_type%3D%22regression%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_raw%5B%22smiles%22%5D.to_list()%2C%20_train_raw%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_val_raw%5B%22smiles%22%5D.to_list()%2C%20%20%20_val_raw%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds%20%3D%20_m.predict(_test_raw%5B%22smiles%22%5D.to_list())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B_TARGET_COL%5D.to_numpy().tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20_model_key%2C%20%22method%22%3A%20_model_key%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20_comp_df%20%3D%20pl.DataFrame(_all_records)%0A%20%20%20%20%20%20%20%20_COMP_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_COMP_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_comp_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%E2%86%92%20%7B_COMP_PATH_GZ%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Metrics%20and%20Tukey%20HSD%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_metrics_comp%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_comp_df.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary_comp%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics_comp.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22rho%22%2C%20%22r2%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_result_tab%2C%20_df_means%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20_metrics_comp%2C%20%22mae%22%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20sort%3DTrue%2C%20direction_dict%3D%7B%22mae%22%3A%20%22minimize%22%7D%2C%0A%20%20%20%20)%0A%20%20%20%20_sig%20%3D%20_result_tab%5B_result_tab%5B%22p-adj%22%5D%20%3C%200.05%5D%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20CLI%20vs%20API%20%E2%80%94%20mean%20CV%20metrics%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary_comp.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Tukey%20HSD%20on%20MAE%20%E2%80%94%20expecting%20**no%20significant%20difference**%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sig%5B%5B%22group1%22%2C%20%22group2%22%2C%20%22meandiff%22%2C%20%22p-adj%22%5D%5D.to_string()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(_sig)%20%3E%200%20else%20%22%20%20No%20significant%20difference%20(p%20%3E%200.05)%20%E2%9C%93%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20accuracy_score%2C%0A%20%20%20%20balanced_accuracy_score%2C%0A%20%20%20%20f1_score%2C%0A%20%20%20%20matthews_corrcoef%2C%0A%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20mean_squared_error%2C%0A%20%20%20%20np%2C%0A%20%20%20%20precision_score%2C%0A%20%20%20%20r2_score%2C%0A%20%20%20%20recall_score%2C%0A%20%20%20%20roc_auc_score%2C%0A%20%20%20%20spearmanr%2C%0A%20%20%20%20warnings%2C%0A)%3A%0A%20%20%20%20_classification_metrics%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22accuracy%22%3A%20accuracy_score%2C%0A%20%20%20%20%20%20%20%20%22balanced_accuracy%22%3A%20balanced_accuracy_score%2C%0A%20%20%20%20%20%20%20%20%22precision%22%3A%20precision_score%2C%0A%20%20%20%20%20%20%20%20%22recall%22%3A%20recall_score%2C%0A%20%20%20%20%20%20%20%20%22f1%22%3A%20f1_score%2C%0A%20%20%20%20%20%20%20%20%22mcc%22%3A%20matthews_corrcoef%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20_safe_spearmanr(y_true%2C%20y_pred)%3A%0A%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20warnings.simplefilter(%22ignore%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20spearmanr(y_true%2C%20y_pred).correlation%0A%0A%20%20%20%20_regression_metrics%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22r2%22%3A%20r2_score%2C%0A%20%20%20%20%20%20%20%20%22rho%22%3A%20lambda%20y_true%2C%20y_pred%3A%20_safe_spearmanr(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%22mse%22%3A%20mean_squared_error%2C%0A%20%20%20%20%20%20%20%20%22rmse%22%3A%20lambda%20y_true%2C%20y_pred%3A%20np.sqrt(mean_squared_error(y_true%2C%20y_pred))%2C%0A%20%20%20%20%20%20%20%20%22mae%22%3A%20mean_absolute_error%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20evaluate_predictions(%0A%20%20%20%20%20%20%20%20y_pred%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20y_test%3A%20np.ndarray%2C%0A%20%20%20%20%20%20%20%20pred_type%3A%20str%2C%0A%20%20%20%20%20%20%20%20thr%3A%20float%20%3D%200.5%2C%0A%20%20%20%20)%20-%3E%20dict%5Bstr%2C%20float%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Compute%20a%20standard%20set%20of%20metrics%20for%20either%20classification%20or%20regression.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred%3A%20Model%20predictions.%20For%20classification%2C%20these%20should%20be%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20probability%20scores%20(0%E2%80%931)%3B%20for%20regression%2C%20continuous%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20y_test%3A%20Ground-truth%20labels%20or%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3A%20%22classification%22%20or%20%22regression%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20thr%3A%20Decision%20threshold%20applied%20to%20y_pred%20for%20binary%20classification%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metrics.%20Ignored%20for%20regression.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Dictionary%20mapping%20metric%20names%20to%20their%20computed%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20Classification%20metrics%3A%20accuracy%2C%20balanced_accuracy%2C%20precision%2C%20recall%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20f1%2C%20mcc%2C%20roc_auc.%20Regression%20metrics%3A%20r2%2C%20rho%2C%20mse%2C%20rmse%2C%20mae.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20pred_type%20%3D%3D%20%22classification%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20out%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20_classification_metrics%5Bmetric%5D(y_test%2C%20y_pred%20%3E%20thr)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20metric%20in%20_classification_metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20out%5B%22roc_auc%22%5D%20%3D%20roc_auc_score(y_test%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20out%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20_regression_metrics%5Bmetric%5D(y_test%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20metric%20in%20_regression_metrics%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20AtomPairFingerprint%2C%0A%20%20%20%20AvalonFingerprint%2C%0A%20%20%20%20ConformerGenerator%2C%0A%20%20%20%20E3FPFingerprint%2C%0A%20%20%20%20ECFPFingerprint%2C%0A%20%20%20%20MACCSFingerprint%2C%0A%20%20%20%20MQNsFingerprint%2C%0A%20%20%20%20MolFromSmilesTransformer%2C%0A%20%20%20%20MordredFingerprint%2C%0A%20%20%20%20PubChemFingerprint%2C%0A%20%20%20%20RDKitFingerprint%2C%0A%20%20%20%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20_fp_dict%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22ecfp%22%3A%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22morgan%22%3A%20ECFPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22maccs%22%3A%20MACCSFingerprint%2C%0A%20%20%20%20%20%20%20%20%22torsion%22%3A%20TopologicalTorsionFingerprint%2C%0A%20%20%20%20%20%20%20%20%22rdkit%22%3A%20RDKitFingerprint%2C%0A%20%20%20%20%20%20%20%20%22atompair%22%3A%20AtomPairFingerprint%2C%0A%20%20%20%20%20%20%20%20%22avalon%22%3A%20AvalonFingerprint%2C%0A%20%20%20%20%20%20%20%20%22e3fp%22%3A%20E3FPFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mordred%22%3A%20MordredFingerprint%2C%0A%20%20%20%20%20%20%20%20%22mqn%22%3A%20MQNsFingerprint%2C%0A%20%20%20%20%20%20%20%20%22pubchem%22%3A%20PubChemFingerprint%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20generate_fingerprint(df%3A%20pl.DataFrame%2C%20fingerprint_type%3A%20str%2C%20**kwargs)%20-%3E%20pl.DataFrame%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Generate%20molecular%20fingerprints%20and%20add%20them%20as%20a%20new%20column%20to%20the%20DataFrame.%0A%0A%20%20%20%20%20%20%20%20Dispatches%20to%20the%20appropriate%20fingerprint%20class%20based%20on%20fingerprint_type.%0A%20%20%20%20%20%20%20%20All%20skfp%20types%20use%20their%20standard%20constructor%20%2F%20transform%20pipeline.%0A%20%20%20%20%20%20%20%20For%20fingerprint%20types%20that%20require%203D%20conformers%20(e.g.%2C%20E3FP)%2C%20conformers%20are%0A%20%20%20%20%20%20%20%20generated%20automatically%20via%20RDKit%20ETKDGv3.%0A%0A%20%20%20%20%20%20%20%20CheMeleon%20embeddings%20are%20not%20handled%20here%20%E2%80%94%20use%20the%20%60chemeleon_embed%60%0A%20%20%20%20%20%20%20%20function%20which%20runs%20inference%20in%20an%20isolated%20subprocess.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20containing%20a%20%22smiles%22%20column.%0A%20%20%20%20%20%20%20%20%20%20%20%20fingerprint_type%3A%20One%20of%3A%20%22ecfp%22%2F%22morgan%22%2C%20%22maccs%22%2C%20%22torsion%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rdkit%22%2C%20%22atompair%22%2C%20%22avalon%22%2C%20%22e3fp%22%2C%20%22mordred%22%2C%20%22mqn%22%2C%20%22pubchem%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20**kwargs%3A%20Additional%20keyword%20arguments%20forwarded%20to%20the%20fingerprint%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20class%20constructor%20(e.g.%2C%20radius%3D3%2C%20n_bits%3D1024%20for%20ECFP).%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20DataFrame%20with%20an%20added%20column%20named%20after%20fingerprint_type.%0A%0A%20%20%20%20%20%20%20%20Raises%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ValueError%3A%20If%20fingerprint_type%20is%20not%20a%20recognized%20key.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20fingerprint_type%20not%20in%20_fp_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Fingerprint%20type%20not%20recognized%3A%20%7Bfingerprint_type!r%7D.%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22Valid%20values%3A%20%7Blist(_fp_dict.keys())%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20smiles_list%20%3D%20df.get_column(%22smiles%22).to_list()%0A%0A%20%20%20%20%20%20%20%20%23%20All%20fingerprint%20types%20follow%20the%20skfp%20constructor%20%2F%20transform%20pattern.%0A%20%20%20%20%20%20%20%20fp_func%20%3D%20_fp_dict%5Bfingerprint_type%5D(**kwargs)%0A%0A%20%20%20%20%20%20%20%20if%20fp_func.requires_conformers%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mol_from_smiles%20%3D%20MolFromSmilesTransformer()%0A%20%20%20%20%20%20%20%20%20%20%20%20conf_gen%20%3D%20ConformerGenerator()%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20mol_from_smiles.transform(smiles_list)%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20conf_gen.transform(mols_list)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20mols_list%20%3D%20smiles_list%0A%0A%20%20%20%20%20%20%20%20fps%20%3D%20fp_func.transform(mols_list)%0A%20%20%20%20%20%20%20%20return%20df.with_columns(pl.Series(values%3Dfps%2C%20name%3Dfingerprint_type))%0A%0A%20%20%20%20return%20(generate_fingerprint%2C)%0A%0A%0A%40app.cell%0Adef%20_(BaseKFold%2C%20Iterator%2C%20Optional%2C%20np%2C%20pl)%3A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20helpers%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20def%20split_dataset_random(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20p_test%3A%20float%20%3D%200.2%2C%0A%20%20%20%20%20%20%20%20seed%3A%20int%20%3D%2042%2C%0A%20%20%20%20)%20-%3E%20tuple%5Bpl.DataFrame%2C%20pl.DataFrame%5D%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Randomly%20split%20a%20DataFrame%20into%20train%20and%20test%20subsets.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Input%20DataFrame.%0A%20%20%20%20%20%20%20%20%20%20%20%20p_test%3A%20Fraction%20of%20rows%20allocated%20to%20the%20test%20set.%0A%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20Random%20seed%20for%20reproducibility.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20of%20(train_df%2C%20test_df).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20rng%20%3D%20np.random.default_rng(seed)%0A%20%20%20%20%20%20%20%20idx%20%3D%20rng.permutation(df.shape%5B0%5D)%0A%20%20%20%20%20%20%20%20n_test%20%3D%20int(len(idx)%20*%20p_test)%0A%20%20%20%20%20%20%20%20test_idx%2C%20train_idx%20%3D%20idx%5B%3An_test%5D%2C%20idx%5Bn_test%3A%5D%0A%20%20%20%20%20%20%20%20return%20df%5Btrain_idx%5D.clone()%2C%20df%5Btest_idx%5D.clone()%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20GroupKFoldShuffle%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%0A%20%20%20%20class%20GroupKFoldShuffle(BaseKFold)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20K-fold%20cross-validator%20that%20respects%20group%20boundaries%20and%20supports%20shuffling.%0A%0A%20%20%20%20%20%20%20%20An%20extension%20of%20scikit-learn's%20GroupKFold%20that%20adds%20optional%20shuffling%20of%0A%20%20%20%20%20%20%20%20groups%20before%20splitting.%20Useful%20for%20scaffold-aware%20cross-validation%20where%0A%20%20%20%20%20%20%20%20you%20want%20reproducible%20but%20shuffled%20group%20assignments.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20n_splits%3A%20Number%20of%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20shuffle%3A%20Whether%20to%20shuffle%20groups%20before%20splitting.%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Random%20seed%20used%20when%20shuffle%3DTrue.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%0A%20%20%20%20%20%20%20%20def%20__init__(%0A%20%20%20%20%20%20%20%20%20%20%20%20self%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20n_splits%3A%20int%20%3D%205%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20*%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20shuffle%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20random_state%3A%20Optional%5Bint%5D%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20super().__init__(n_splits%3Dn_splits%2C%20shuffle%3Dshuffle%2C%20random_state%3Drandom_state)%0A%0A%20%20%20%20%20%20%20%20def%20split(self%2C%20X%2C%20y%3DNone%2C%20groups%3DNone)%20-%3E%20Iterator%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Collect%20unique%20groups%2C%20then%20optionally%20shuffle%20them%20so%20that%20fold%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20assignment%20is%20randomised%20while%20still%20keeping%20each%20group%20intact.%0A%20%20%20%20%20%20%20%20%20%20%20%20unique_groups%20%3D%20np.unique(groups)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20self.shuffle%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rng%20%3D%20np.random.default_rng(self.random_state)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20unique_groups%20%3D%20rng.permutation(unique_groups)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Distribute%20groups%20as%20evenly%20as%20possible%20across%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20split_groups%20%3D%20np.array_split(unique_groups%2C%20self.n_splits)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20test_group_ids%20in%20split_groups%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test_mask%20%3D%20np.isin(groups%2C%20test_group_ids)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train_mask%20%3D%20~test_mask%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20yield%20np.where(train_mask)%5B0%5D%2C%20np.where(test_mask)%5B0%5D%0A%0A%20%20%20%20return%20GroupKFoldShuffle%2C%20split_dataset_random%0A%0A%0A%40app.cell%0Adef%20_(GroupKFoldShuffle%2C%20Iterator%2C%20pl%2C%20split_dataset_random)%3A%0A%20%20%20%20def%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20n_outer%3A%20int%20%3D%205%2C%0A%20%20%20%20%20%20%20%20n_inner%3A%20int%20%3D%205%2C%0A%20%20%20%20%20%20%20%20seed%3A%20int%20%3D%2042%2C%0A%20%20%20%20%20%20%20%20p_val%3A%20float%20%3D%200%2C%0A%20%20%20%20)%20-%3E%20Iterator%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Generate%20nested%205%C3%975%20CV%20splits%20using%20a%20**random**%20molecule%20assignment.%0A%0A%20%20%20%20%20%20%20%20Each%20molecule%20is%20treated%20as%20its%20own%20group%2C%20so%20folds%20are%20purely%20random.%0A%20%20%20%20%20%20%20%20This%20is%20the%20baseline%20split%20strategy%3A%20it%20gives%20optimistic%20estimates%20of%0A%20%20%20%20%20%20%20%20generalisation%20because%20train%20and%20test%20scaffolds%20can%20overlap.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20to%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20n_outer%3A%20Number%20of%20outer%20CV%20folds.%0A%20%20%20%20%20%20%20%20%20%20%20%20n_inner%3A%20Number%20of%20inner%20CV%20folds%20per%20outer%20iteration.%0A%20%20%20%20%20%20%20%20%20%20%20%20seed%3A%20Random%20seed%20for%20GroupKFoldShuffle.%0A%20%20%20%20%20%20%20%20%20%20%20%20p_val%3A%20Fraction%20of%20the%20training%20set%20reserved%20as%20a%20validation%20split.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%200%20disables%20the%20validation%20split%20(val_df%20is%20yielded%20as%20None).%0A%0A%20%20%20%20%20%20%20%20Yields%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuples%20of%20(fold_index%2C%20outer_index%2C%20inner_index%2C%20train_df%2C%20val_df%2C%20test_df).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(n_outer)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20kf%20%3D%20GroupKFoldShuffle(n_splits%3Dn_inner%2C%20random_state%3Dseed%20%2B%20i%2C%20shuffle%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20groups%20%3D%20list(range(df.shape%5B0%5D))%20%20%23%20each%20molecule%20is%20its%20own%20group%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%2C%20(train_idx%2C%20test_idx)%20in%20enumerate(kf.split(df%2C%20groups%3Dgroups))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fold%20%3D%20i%20*%20n_inner%20%2B%20j%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train%20%3D%20df%5Btrain_idx%5D.clone()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20test%20%3D%20df%5Btest_idx%5D.clone()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20val%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20p_val%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20train%2C%20val%20%3D%20split_dataset_random(train%2C%20p_test%3Dp_val%2C%20seed%3Dseed%20%2B%20fold)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20yield%20fold%2C%20i%2C%20j%2C%20train%2C%20val%2C%20test%0A%0A%20%20%20%20return%20(generate_cv_splits_random%2C)%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20ML%20comparison%20code%0A%0A%20%20%20%20Adapted%20from%20https%3A%2F%2Fgithub.com%2Fpolaris-hub%2Fpolaris-method-comparison%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20mean_absolute_error%2C%0A%20%20%20%20mean_squared_error%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20precision_score%2C%0A%20%20%20%20r2_score%2C%0A%20%20%20%20recall_score%2C%0A%20%20%20%20spearmanr%2C%0A%20%20%20%20warnings%2C%0A)%3A%0A%20%20%20%20def%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20cycle_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20val_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20pred_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20thresh%3A%20float%2C%0A%20%20%20%20)%20-%3E%20pl.DataFrame%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Calculate%20regression%20metrics%20(MAE%2C%20MSE%2C%20R2%2C%20rho%2C%20prec%2C%20recall)%20for%20each%20method%20and%20split.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bmethod%2C%20split%5D%20plus%20the%20columns%20named%20in%20the%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20remaining%20arguments.%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3A%20Column%20indicating%20the%20cross-validation%20fold.%0A%20%20%20%20%20%20%20%20%20%20%20%20val_col%3A%20Column%20with%20the%20ground%20truth%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20pred_col%3A%20Column%20with%20the%20model%20predictions.%0A%20%20%20%20%20%20%20%20%20%20%20%20thresh%3A%20Decision%20threshold%20used%20to%20binarise%20continuous%20values%20for%20precision%2Frecall.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20method%2C%20split%2C%20mae%2C%20mse%2C%20r2%2C%20rho%2C%20prec%2C%20recall%5D.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Drop%20rows%20where%20predictions%20are%20NaN%20(e.g.%20Macau%20numerical%20instability)%0A%20%20%20%20%20%20%20%20%23%20before%20any%20metric%20computation%20to%20avoid%20downstream%20sklearn%20errors.%0A%20%20%20%20%20%20%20%20df_in%20%3D%20df.filter(pl.col(pred_col).is_not_nan()%20%26%20pl.col(pred_col).is_not_null())%0A%20%20%20%20%20%20%20%20%23%20Derive%20binary%20class%20columns%20from%20the%20continuous%20threshold%0A%20%20%20%20%20%20%20%20df_in%20%3D%20df_in.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(val_col)%20%3E%20thresh).alias(%22true_class%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(pred_col)%20%3E%20thresh).alias(%22pred_class%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%0A%20%20%20%20%20%20%20%20%23%20Ensure%20the%20threshold%20actually%20produces%20two%20distinct%20classes%0A%20%20%20%20%20%20%20%20assert%20df_in%5B%22true_class%22%5D.n_unique()%20%3D%3D%202%2C%20%22Binary%20classification%20requires%20two%20classes%22%0A%0A%20%20%20%20%20%20%20%20metric_list%3A%20list%5Bdict%5D%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Iterate%20over%20each%20(cycle%2C%20method%2C%20split)%20group%20and%20compute%20metrics%0A%20%20%20%20%20%20%20%20for%20group_keys%2C%20group_df%20in%20df_in.group_by(%5Bcycle_col%2C%20%22method%22%2C%20%22split%22%5D)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle%2C%20method%2C%20split%20%3D%20group_keys%0A%20%20%20%20%20%20%20%20%20%20%20%20y_true%20%3D%20group_df%5Bval_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred%20%3D%20group_df%5Bpred_col%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_true_cls%20%3D%20group_df%5B%22true_class%22%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20y_pred_cls%20%3D%20group_df%5B%22pred_class%22%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20warnings.simplefilter(%22ignore%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20rho%2C%20_%20%3D%20spearmanr(y_true%2C%20y_pred)%0A%20%20%20%20%20%20%20%20%20%20%20%20metric_list.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22cv_cycle%22%3A%20cycle%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20method%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22split%22%3A%20split%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20mean_absolute_error(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mse%22%3A%20mean_squared_error(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22r2%22%3A%20r2_score(y_true%2C%20y_pred)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rho%22%3A%20float(rho)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22prec%22%3A%20precision_score(y_true_cls%2C%20y_pred_cls)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22recall%22%3A%20recall_score(y_true_cls%2C%20y_pred_cls)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20return%20pl.DataFrame(metric_list)%0A%0A%20%20%20%20return%20(calc_regression_metrics%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20np%2C%20pd%2C%20pg%2C%20pl%2C%20psturng%2C%20qsturng%2C%20warnings)%3A%0A%20%20%20%20def%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20df%3A%20pl.DataFrame%2C%0A%20%20%20%20%20%20%20%20metric%3A%20str%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20alpha%3A%20float%20%3D%200.05%2C%0A%20%20%20%20%20%20%20%20sort%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20direction_dict%3A%20Optional%5Bdict%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20tuple%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Perform%20repeated%20measures%20Tukey%20HSD%20test%20on%20the%20given%20Polars%20DataFrame.%0A%0A%20%20%20%20%20%20%20%20Internally%20converts%20to%20pandas%20for%20pingouin%2Fstatsmodels%20compatibility.%0A%20%20%20%20%20%20%20%20All%20returned%20DataFrames%20are%20pandas%20objects%20for%20downstream%20seaborn%20plotting.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20columns%20%5Bcv_cycle%2C%20group_col%2C%20metric%5D.%0A%20%20%20%20%20%20%20%20%20%20%20%20metric%3A%20Column%20name%20of%20the%20metric%20to%20test.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20name%20indicating%20the%20comparison%20groups.%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3A%20Significance%20level%20for%20the%20test.%0A%20%20%20%20%20%20%20%20%20%20%20%20sort%3A%20Whether%20to%20sort%20groups%20by%20their%20mean%20metric%20value.%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict%3A%20Maps%20metric%20names%20to%20%22maximize%22%20or%20%22minimize%22%20for%20sort%20direction.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Tuple%20of%20(result_tab%2C%20df_means%2C%20df_means_diff%2C%20pc)%20%E2%80%94%20all%20pandas%20DataFrames.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20result_tab%3A%20Pairwise%20comparisons%20with%20adjusted%20p-values.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20df_means%3A%20Mean%20values%20per%20group.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20df_means_diff%3A%20Matrix%20of%20pairwise%20mean%20differences.%0A%20%20%20%20%20%20%20%20%20%20%20%20-%20pc%3A%20Matrix%20of%20adjusted%20p-values.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20%23%20Convert%20to%20pandas%20%E2%80%94%20pingouin%20and%20statsmodels%20require%20it%0A%20%20%20%20%20%20%20%20df_pd%20%3D%20df.to_pandas()%0A%0A%20%20%20%20%20%20%20%20if%20sort%20and%20direction_dict%20and%20metric%20in%20direction_dict%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20direction_dict%5Bmetric%5D%20%3D%3D%20'maximize'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue).sort_values(metric%2C%20ascending%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20elif%20direction_dict%5Bmetric%5D%20%3D%3D%20'minimize'%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue).sort_values(metric%2C%20ascending%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError(%22Invalid%20direction.%20Expected%20'maximize'%20or%20'minimize'.%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df_means%20%3D%20df_pd.groupby(group_col).mean(numeric_only%3DTrue)%0A%0A%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20warnings.filterwarnings('ignore'%2C%20category%3DRuntimeWarning%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20message%3D'divide%20by%20zero%20encountered%20in%20scalar%20divide')%0A%20%20%20%20%20%20%20%20%20%20%20%20aov%20%3D%20pg.rm_anova(dv%3Dmetric%2C%20within%3Dgroup_col%2C%20subject%3D'cv_cycle'%2C%20data%3Ddf_pd%2C%20detailed%3DTrue)%0A%20%20%20%20%20%20%20%20mse%20%3D%20aov.loc%5B1%2C%20'MS'%5D%0A%20%20%20%20%20%20%20%20df_resid%20%3D%20aov.loc%5B1%2C%20'DF'%5D%0A%0A%20%20%20%20%20%20%20%20methods%20%3D%20df_means.index%0A%20%20%20%20%20%20%20%20n_groups%20%3D%20len(methods)%0A%20%20%20%20%20%20%20%20n_per_group%20%3D%20df_pd%5Bgroup_col%5D.value_counts().mean()%0A%0A%20%20%20%20%20%20%20%20tukey_se%20%3D%20np.sqrt(2%20*%20mse%20%2F%20n_per_group)%0A%20%20%20%20%20%20%20%20q%20%3D%20qsturng(1%20-%20alpha%2C%20n_groups%2C%20df_resid)%0A%0A%20%20%20%20%20%20%20%20num_comparisons%20%3D%20len(methods)%20*%20(len(methods)%20-%201)%20%2F%2F%202%0A%20%20%20%20%20%20%20%20result_tab%20%3D%20pd.DataFrame(index%3Drange(num_comparisons)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20columns%3D%5B%22group1%22%2C%20%22group2%22%2C%20%22meandiff%22%2C%20%22lower%22%2C%20%22upper%22%2C%20%22p-adj%22%5D)%0A%0A%20%20%20%20%20%20%20%20df_means_diff%20%3D%20pd.DataFrame(index%3Dmethods%2C%20columns%3Dmethods%2C%20data%3D0.0)%0A%20%20%20%20%20%20%20%20pc%20%3D%20pd.DataFrame(index%3Dmethods%2C%20columns%3Dmethods%2C%20data%3D1.0)%0A%0A%20%20%20%20%20%20%20%20%23%20Calculate%20pairwise%20mean%20differences%20and%20adjusted%20p-values%0A%20%20%20%20%20%20%20%20row_idx%20%3D%200%0A%20%20%20%20%20%20%20%20for%20i%2C%20method1%20in%20enumerate(methods)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20j%2C%20method2%20in%20enumerate(methods)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20group1%20%3D%20df_pd%5Bdf_pd%5Bgroup_col%5D%20%3D%3D%20method1%5D%5Bmetric%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20group2%20%3D%20df_pd%5Bdf_pd%5Bgroup_col%5D%20%3D%3D%20method2%5D%5Bmetric%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20mean_diff%20%3D%20group1.mean()%20-%20group2.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20studentized_range%20%3D%20np.abs(mean_diff)%20%2F%20tukey_se%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20adjusted_p%20%3D%20psturng(studentized_range%20*%20np.sqrt(2)%2C%20n_groups%2C%20df_resid)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20isinstance(adjusted_p%2C%20np.ndarray)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20adjusted_p%20%3D%20adjusted_p%5B0%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20lower%20%3D%20mean_diff%20-%20(q%20%2F%20np.sqrt(2)%20*%20tukey_se)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20upper%20%3D%20mean_diff%20%2B%20(q%20%2F%20np.sqrt(2)%20*%20tukey_se)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20result_tab.loc%5Brow_idx%5D%20%3D%20%5Bmethod1%2C%20method2%2C%20mean_diff%2C%20lower%2C%20upper%2C%20adjusted_p%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pc.loc%5Bmethod1%2C%20method2%5D%20%3D%20adjusted_p%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pc.loc%5Bmethod2%2C%20method1%5D%20%3D%20adjusted_p%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means_diff.loc%5Bmethod1%2C%20method2%5D%20%3D%20mean_diff%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df_means_diff.loc%5Bmethod2%2C%20method1%5D%20%3D%20-mean_diff%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20row_idx%20%2B%3D%201%0A%0A%20%20%20%20%20%20%20%20df_means_diff%20%3D%20df_means_diff.astype(float)%0A%0A%20%20%20%20%20%20%20%20result_tab%5B%22group1_mean%22%5D%20%3D%20result_tab%5B%22group1%22%5D.map(df_means%5Bmetric%5D)%0A%20%20%20%20%20%20%20%20result_tab%5B%22group2_mean%22%5D%20%3D%20result_tab%5B%22group2%22%5D.map(df_means%5Bmetric%5D)%0A%0A%20%20%20%20%20%20%20%20result_tab.index%20%3D%20result_tab%5B'group1'%5D%20%2B%20'%20-%20'%20%2B%20result_tab%5B'group2'%5D%0A%0A%20%20%20%20%20%20%20%20return%20result_tab%2C%20df_means%2C%20df_means_diff%2C%20pc%0A%0A%20%20%20%20return%20(rm_tukey_hsd%2C)%0A%0A%0A%40app.cell%0Adef%20_(Optional%2C%20Path%2C%20math%2C%20np%2C%20plt%2C%20rm_tukey_hsd%2C%20sns)%3A%0A%20%20%20%20def%20mcs_plot(pc%2C%20effect_size%2C%20means%2C%20labels%3DTrue%2C%20cmap%3DNone%2C%20cbar_ax_bbox%3DNone%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax%3DNone%2C%20show_diff%3DTrue%2C%20cell_text_size%3D16%2C%20axis_text_size%3D12%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20show_cbar%3DTrue%2C%20reverse_cmap%3DFalse%2C%20vlim%3DNone%2C%20**kwargs)%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Multiple%20comparison%20of%20means%20heatmap%20(Tukey%20HSD).%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pc%3A%20DataFrame%20of%20adjusted%20p-values%20(groups%20%C3%97%20groups).%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_size%3A%20DataFrame%20of%20pairwise%20mean%20differences.%0A%20%20%20%20%20%20%20%20%20%20%20%20means%3A%20Series%20of%20mean%20metric%20value%20per%20group.%0A%20%20%20%20%20%20%20%20%20%20%20%20labels%3A%20Show%20axis%20labels%20with%20mean%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%3A%20Colormap%20name%20(default%20%22coolwarm%22).%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%3A%20Existing%20Axes%20to%20draw%20on.%0A%20%20%20%20%20%20%20%20%20%20%20%20show_diff%3A%20Annotate%20cells%20with%20mean%20difference%20values.%0A%20%20%20%20%20%20%20%20%20%20%20%20cell_text_size%3A%20Font%20size%20for%20cell%20annotations.%0A%20%20%20%20%20%20%20%20%20%20%20%20axis_text_size%3A%20Font%20size%20for%20axis%20tick%20labels.%0A%20%20%20%20%20%20%20%20%20%20%20%20show_cbar%3A%20Show%20colorbar.%0A%20%20%20%20%20%20%20%20%20%20%20%20reverse_cmap%3A%20Reverse%20the%20colormap%20(use%20for%20metrics%20to%20minimise).%0A%20%20%20%20%20%20%20%20%20%20%20%20vlim%3A%20Symmetric%20colour%20scale%20limit%20(colours%20span%20%E2%88%922%C3%97vlim%20to%20%2B2%C3%97vlim).%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20The%20Axes%20with%20the%20heatmap.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'cbar'%2C%20'vmin'%2C%20'vmax'%2C%20'center'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20kwargs.pop(key%2C%20None)%0A%0A%20%20%20%20%20%20%20%20if%20not%20cmap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%20%3D%20%22coolwarm%22%0A%20%20%20%20%20%20%20%20if%20reverse_cmap%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20cmap%20%3D%20cmap%20%2B%20%22_r%22%0A%0A%20%20%20%20%20%20%20%20significance%20%3D%20pc.copy().astype(object)%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.001)%20%26%20(pc%20%3E%3D%200)%5D%20%3D%20'***'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.01)%20%20%26%20(pc%20%3E%3D%200.001)%5D%20%3D%20'**'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3C%200.05)%20%20%26%20(pc%20%3E%3D%200.01)%5D%20%20%3D%20'*'%0A%20%20%20%20%20%20%20%20significance%5B(pc%20%3E%3D%200.05)%5D%20%3D%20''%0A%20%20%20%20%20%20%20%20np.fill_diagonal(significance.values%2C%20'')%0A%0A%20%20%20%20%20%20%20%20annotations%20%3D%20effect_size.round(2).astype(str)%20%2B%20significance%20if%20show_diff%20else%20significance%0A%0A%20%20%20%20%20%20%20%20hax%20%3D%20sns.heatmap(%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_size%2C%20cmap%3Dcmap%2C%20annot%3Dannotations%2C%20fmt%3D''%2C%20cbar%3Dshow_cbar%2C%20ax%3Dax%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20annot_kws%3D%7B%22size%22%3A%20cell_text_size%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20vmin%3D-2%20*%20vlim%20if%20vlim%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20vmax%3D%202%20*%20vlim%20if%20vlim%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20**kwargs%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20labels%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20label_list%20%3D%20list(means.index)%0A%20%20%20%20%20%20%20%20%20%20%20%20hax.set_xticklabels(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bx%20%2B%20f'%5Cn%7Bmeans.loc%5Bx%5D%3A.2f%7D'%20for%20x%20in%20label_list%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20size%3Daxis_text_size%2C%20ha%3D'center'%2C%20va%3D'top'%2C%20rotation%3D0%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20hax.set_yticklabels(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bx%20%2B%20f'%5Cn%7Bmeans.loc%5Bx%5D%3A.2f%7D%5Cn'%20for%20x%20in%20label_list%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20size%3Daxis_text_size%2C%20ha%3D'center'%2C%20va%3D'center'%2C%20rotation%3D90%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20hax.set_xlabel('')%0A%20%20%20%20%20%20%20%20hax.set_ylabel('')%0A%20%20%20%20%20%20%20%20return%20hax%0A%0A%20%20%20%20def%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20df%2C%0A%20%20%20%20%20%20%20%20stats%3A%20list%5Bstr%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3A%20str%2C%0A%20%20%20%20%20%20%20%20alpha%3A%20float%20%3D%200.05%2C%0A%20%20%20%20%20%20%20%20figsize%3A%20tuple%20%3D%20(20%2C%2010)%2C%0A%20%20%20%20%20%20%20%20direction_dict%3A%20dict%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20effect_dict%3A%20dict%20%7C%20None%20%3D%20None%2C%0A%20%20%20%20%20%20%20%20show_diff%3A%20bool%20%3D%20True%2C%0A%20%20%20%20%20%20%20%20cell_text_size%3A%20int%20%3D%2016%2C%0A%20%20%20%20%20%20%20%20axis_text_size%3A%20int%20%3D%2012%2C%0A%20%20%20%20%20%20%20%20title_text_size%3A%20int%20%3D%2016%2C%0A%20%20%20%20%20%20%20%20sort_axes%3A%20bool%20%3D%20False%2C%0A%20%20%20%20%20%20%20%20save_path%3A%20Optional%5BPath%5D%20%3D%20None%2C%0A%20%20%20%20)%20-%3E%20plt.Figure%3A%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Grid%20of%20Tukey%20HSD%20MCS%20heatmaps%2C%20one%20panel%20per%20metric.%0A%0A%20%20%20%20%20%20%20%20Args%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%3A%20Polars%20DataFrame%20with%20%5Bcv_cycle%2C%20group_col%5D%20and%20metric%20columns.%0A%20%20%20%20%20%20%20%20%20%20%20%20stats%3A%20Metric%20names%20to%20plot.%0A%20%20%20%20%20%20%20%20%20%20%20%20group_col%3A%20Column%20that%20identifies%20comparison%20groups.%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3A%20Significance%20threshold.%0A%20%20%20%20%20%20%20%20%20%20%20%20figsize%3A%20Figure%20size.%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict%3A%20Maps%20metric%20%E2%86%92%20%22maximize%22%20or%20%22minimize%22.%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_dict%3A%20Maps%20metric%20%E2%86%92%20colour-scale%20half-width.%0A%20%20%20%20%20%20%20%20%20%20%20%20show_diff%3A%20Show%20mean%20differences%20in%20cell%20annotations.%0A%20%20%20%20%20%20%20%20%20%20%20%20cell_text_size%3A%20Annotation%20font%20size.%0A%20%20%20%20%20%20%20%20%20%20%20%20axis_text_size%3A%20Tick%20label%20font%20size.%0A%20%20%20%20%20%20%20%20%20%20%20%20title_text_size%3A%20Panel%20title%20font%20size.%0A%20%20%20%20%20%20%20%20%20%20%20%20sort_axes%3A%20Sort%20groups%20by%20mean%20metric%20value.%0A%20%20%20%20%20%20%20%20%20%20%20%20save_path%3A%20Save%20figure%20to%20this%20path%20if%20provided.%0A%0A%20%20%20%20%20%20%20%20Returns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20Matplotlib%20Figure.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20if%20direction_dict%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict%20%3D%20%7B%7D%0A%20%20%20%20%20%20%20%20if%20effect_dict%20is%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_dict%20%3D%20%7B%7D%0A%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%2C%20'mae'%2C%20'mse'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20direction_dict.setdefault(key%2C%20'maximize'%20if%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%5D%20else%20'minimize')%0A%20%20%20%20%20%20%20%20for%20key%20in%20%5B'r2'%2C%20'rho'%2C%20'prec'%2C%20'recall'%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20effect_dict.setdefault(key%2C%200.1)%0A%20%20%20%20%20%20%20%20effect_dict.setdefault('mae'%2C%200.5)%0A%20%20%20%20%20%20%20%20effect_dict.setdefault('mse'%2C%201.0)%0A%0A%20%20%20%20%20%20%20%20ncol%20%3D%201%20if%20len(stats)%20%3D%3D%201%20else%20(2%20if%20len(stats)%20%3D%3D%204%20else%203)%0A%20%20%20%20%20%20%20%20nrow%20%3D%20math.ceil(len(stats)%20%2F%20ncol)%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(nrow%2C%20ncol%2C%20figsize%3Dfigsize%2C%20squeeze%3DFalse)%0A%0A%20%20%20%20%20%20%20%20for%20i%2C%20stat%20in%20enumerate(stats)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20stat%20%3D%20stat.lower()%0A%20%20%20%20%20%20%20%20%20%20%20%20_%2C%20df_means%2C%20df_means_diff%2C%20pc%20%3D%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20df%2C%20stat%2C%20group_col%2C%20alpha%2C%20sort_axes%2C%20direction_dict%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20mcs_plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pc%2C%20effect_size%3Ddf_means_diff%2C%20means%3Ddf_means%5Bstat%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20show_diff%3Dshow_diff%2C%20ax%3Dax%5Bi%20%2F%2F%20ncol%2C%20i%20%25%20ncol%5D%2C%20cbar%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20cell_text_size%3Dcell_text_size%2C%20axis_text_size%3Daxis_text_size%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20reverse_cmap%3D(direction_dict.get(stat)%20%3D%3D%20'minimize')%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20vlim%3Deffect_dict.get(stat)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%5Bi%20%2F%2F%20ncol%2C%20i%20%25%20ncol%5D.set_title(stat.upper()%2C%20fontsize%3Dtitle_text_size)%0A%0A%20%20%20%20%20%20%20%20for%20i%20in%20range(len(stats)%2C%20nrow%20*%20ncol)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%5Bi%20%2F%2F%20ncol%2C%20i%20%25%20ncol%5D.set_visible(False)%0A%0A%20%20%20%20%20%20%20%20fig.tight_layout()%0A%20%20%20%20%20%20%20%20if%20save_path%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20fig.savefig(save_path%2C%20dpi%3D300%2C%20bbox_inches%3D'tight')%0A%20%20%20%20%20%20%20%20return%20fig%0A%0A%20%20%20%20return%20make_mcs_plot_grid%2C%20mcs_plot%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Analysis%201%20%E2%80%94%20RF%2C%20XGBoost%20and%20Macau%20on%20top-3%20fingerprints%0A%0A%20%20%20%20Compare%20**RandomForestModel**%2C%20**BoostedTreesModel**%20(XGBoost)%20and%0A%20%20%20%20**MacauModel**%20using%20the%20three%20fingerprints%20that%20ranked%20highest%20in%20the%20RF%0A%20%20%20%20fingerprint%20sweep%20from%20notebook%203%3A%0A%0A%20%20%20%20%7C%20Fingerprint%20%7C%20Key%20%7C%20Dimensionality%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20Mordred%202D%20descriptors%20%7C%20%60mordred%60%20(no%203D)%20%7C%201%20613%20%7C%0A%20%20%20%20%7C%20MQN%20counts%20%7C%20%60mqn%60%20%7C%2042%20%7C%0A%20%20%20%20%7C%20CheMeleon%20learned%20embedding%20%7C%20%60chemeleon%60%20%7C%202%20048%20%7C%0A%0A%20%20%20%20Same%205%C3%975%20CV%20protocol%20as%20notebook%203%20(seed%3D42%2C%20p_val%3D0.1).%0A%20%20%20%20CheMeleon%20embeddings%20are%20generated%20in%20an%20isolated%20subprocess%20to%20avoid%20the%0A%20%20%20%20OpenMP%20runtime%20collision%20between%20PyTorch%20and%20sklearn.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gc%2C%20pl)%3A%0A%20%20%20%20%22%22%22Load%20the%20dose-response%20training%20set%20(same%20as%20all%20other%20experiments).%22%22%22%0A%20%20%20%20fp_cmp_train%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22molecule_names%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%20%20%20%20gc.collect()%0A%20%20%20%20return%20(fp_cmp_train%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20BoostedTreesModel%2C%0A%20%20%20%20MacauModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20RandomForestModel%2C%0A%20%20%20%20chemeleon_embed%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20fp_cmp_train%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F4_fp_model_comparison_1.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20CheMeleon%20embedding%20subprocess%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20CheMeleon%20(PyTorch)%20must%20run%20in%20an%20isolated%20subprocess%20to%20avoid%20the%0A%20%20%20%20%23%20OpenMP%20runtime%20collision%20with%20sklearn%2Fsmurff%20in%20the%20parent%20process.%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20model%20%C3%97%20fingerprint%20grid%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Each%20entry%3A%20(model_key%2C%20ModelClass%2C%20model_kwargs%2C%20fp_key%2C%20fp_type%2C%20fp_kwargs)%0A%20%20%20%20%23%20MacauModel%20has%20no%20pred_type%20param%3B%20RF%20and%20XGBoost%20do.%0A%20%20%20%20_GRID%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%22rf%22%2C%20%20%20%20%20RandomForestModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22mordred%22%2C%20%20%22mordred%22%2C%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22rf%22%2C%20%20%20%20%20RandomForestModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22mqn%22%2C%20%20%20%20%20%20%22mqn%22%2C%20%20%20%20%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22rf%22%2C%20%20%20%20%20RandomForestModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22chemeleon%22%2C%22chemeleon%22%2C%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22xgboost%22%2CBoostedTreesModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22mordred%22%2C%20%20%22mordred%22%2C%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22xgboost%22%2CBoostedTreesModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22mqn%22%2C%20%20%20%20%20%20%22mqn%22%2C%20%20%20%20%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22xgboost%22%2CBoostedTreesModel%2C%20%20%7B%22pred_type%22%3A%20%22regression%22%7D%2C%20%22chemeleon%22%2C%22chemeleon%22%2C%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22macau%22%2C%20%20MacauModel%2C%20%20%20%20%20%20%20%20%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mordred%22%2C%20%20%22mordred%22%2C%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22macau%22%2C%20%20MacauModel%2C%20%20%20%20%20%20%20%20%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mqn%22%2C%20%20%20%20%20%20%22mqn%22%2C%20%20%20%20%20%20%7B%7D)%2C%0A%20%20%20%20%20%20%20%20(%22macau%22%2C%20%20MacauModel%2C%20%20%20%20%20%20%20%20%20%7B%7D%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22chemeleon%22%2C%22chemeleon%22%2C%7B%7D)%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20%23%20All%209%20method%C3%97fingerprint%20combinations%20that%20must%20be%20present%20for%20the%20file%0A%20%20%20%20%23%20to%20be%20considered%20complete.%20A%20partial%20checkpoint%20(from%20a%20previous%20run%20that%0A%20%20%20%20%23%20crashed%20mid-way)%20will%20be%20missing%20some%20and%20will%20be%20re-run%20from%20scratch.%0A%20%20%20%20_EXPECTED_METHODS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20f%22%7Bmk%7D_%7Bfk%7D%22%20for%20mk%2C%20_%2C%20_%2C%20fk%2C%20*_%20in%20_GRID%0A%20%20%20%20%7D%0A%20%20%20%20_is_complete%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.exists()%0A%20%20%20%20%20%20%20%20and%20set(pl.read_csv(_PRED_PATH_GZ)%5B%22method%22%5D.unique().to_list())%20%3E%3D%20_EXPECTED_METHODS%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_complete%3A%0A%20%20%20%20%20%20%20%20print(f%22Complete%20predictions%20found%20at%20%7B_PRED_PATH_GZ%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20%20%20%20%20fp_cmp_pred_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20if%20_PRED_PATH_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Partial%2Fincomplete%20file%20found%20%E2%80%94%20removing.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20_PRED_PATH_GZ.unlink()%0A%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%20%20%20%20%20%20%20%20_CKPT_PATH%20%3D%20_PRED_PATH_GZ.with_suffix(%22.ckpt.gz%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Resume%20from%20checkpoint%20if%20one%20exists%20(e.g.%20pass%201%20already%20completed)%0A%20%20%20%20%20%20%20%20if%20_CKPT_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20pl.read_csv(_CKPT_PATH).to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_methods%20%3D%20%7Br%5B%22method%22%5D%20for%20r%20in%20_all_records%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Resuming%20from%20checkpoint%3A%20%7Blen(_all_records)%3A%2C%7D%20rows%2C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22completed%3A%20%7Bsorted(_done_methods)%7D%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_methods%3A%20set%5Bstr%5D%20%3D%20set()%0A%0A%20%20%20%20%20%20%20%20def%20_checkpoint(records%3A%20list%5Bdict%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20records%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20gzip.open(_CKPT_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(records).write_csv(_f)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20pass%201%3A%20all%20models%20on%20chemeleon%20embeddings%20(subprocess%20embed)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%20%20%20%20%23%20Embeddings%20computed%20in%20an%20isolated%20subprocess%20(CheMeleon%20loads%20torch)%3B%0A%20%20%20%20%20%20%20%20%23%20RF%2C%20XGBoost%20and%20Macau%20then%20run%20in-process%20(no%20torch).%0A%20%20%20%20%20%20%20%20_chemeleon_models%20%3D%20%5B(mk%2C%20MC%2C%20mkw)%20for%20mk%2C%20MC%2C%20mkw%2C%20fk%2C%20*_%20in%20_GRID%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20fk%20%3D%3D%20%22chemeleon%22%5D%0A%20%20%20%20%20%20%20%20_chemeleon_todo%20%3D%20%5Bt%20for%20t%20in%20_chemeleon_models%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20f%22%7Bt%5B0%5D%7D_chemeleon%22%20not%20in%20_done_methods%5D%0A%0A%20%20%20%20%20%20%20%20if%20not%20_chemeleon_todo%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Pass%201%20(chemeleon)%20already%20complete%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_pbar_ch%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fp_cmp_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_n_folds%2C%20desc%3D%22CV%20chemeleon%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_%2C%20_test_raw%20in%20_pbar_ch%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_train_ch%2C%20_X_test_ch%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prefix%3D%22a1%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_model_key%2C%20_ModelClass%2C%20_model_kwargs%20in%20_chemeleon_todo%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar_ch.set_postfix(%7B%22fold%22%3A%20_fold%2C%20%22model%22%3A%20_model_key%2C%20%22fp%22%3A%20%22chemeleon%22%7D%2C%20refresh%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20_ModelClass(**_model_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_model_key%20%3D%3D%20%22xgboost%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_train_ch%2C%20_y_train%2C%20_X_test_ch%2C%20_y_true)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_train_ch%2C%20_y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_m.predict(_X_test_ch)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20_model_key%2C%20%22fingerprint%22%3A%20%22chemeleon%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20f%22%7B_model_key%7D_chemeleon%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_X_train_ch%2C%20_X_test_ch%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%20%20%20%20%20%20%20%20%20%20%20%20_checkpoint(_all_records)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Pass%201%20done%20%E2%80%94%20%7Blen(_all_records)%3A%2C%7D%20records%20so%20far%22)%0A%0A%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20pass%202%3A%20mordred%20and%20mqn%20(all%20in-process%20%E2%80%94%20no%20torch%20dependency)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%20%20%20%20_non_ch_fps%20%3D%20%5B(%22mordred%22%2C%20%22mordred%22%2C%20%7B%7D)%2C%20(%22mqn%22%2C%20%22mqn%22%2C%20%7B%7D)%5D%0A%20%20%20%20%20%20%20%20_seen%2C%20_inproc_models%20%3D%20set()%2C%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20mk%2C%20MC%2C%20mkw%2C%20fk%2C%20*_%20in%20_GRID%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20fk%20!%3D%20%22chemeleon%22%20and%20mk%20not%20in%20_seen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_inproc_models.append((mk%2C%20MC%2C%20mkw))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_seen.add(mk)%0A%0A%20%20%20%20%20%20%20%20for%20_fp_col%2C%20_fp_type%2C%20_fp_kwargs%20in%20_non_ch_fps%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_inproc_todo%20%3D%20%5B(mk%2C%20MC%2C%20mkw)%20for%20mk%2C%20MC%2C%20mkw%20in%20_inproc_models%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20f%22%7Bmk%7D_%7B_fp_col%7D%22%20not%20in%20_done_methods%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20_inproc_todo%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Pass%202%20(%7B_fp_col%7D)%20already%20complete%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar_fp%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fp_cmp_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_n_folds%2C%20desc%3Df%22CV%20%7B_fp_col%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_%2C%20_test_raw%20in%20_pbar_fp%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_fp%20%3D%20generate_fingerprint(_train_raw%2C%20_fp_type%2C%20**_fp_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_fp%20%20%3D%20generate_fingerprint(_test_raw%2C%20%20_fp_type%2C%20**_fp_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_train%20%20%3D%20extract_fp_matrix(_train_fp%2C%20_fp_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_test%20%20%20%3D%20extract_fp_matrix(_test_fp%2C%20%20_fp_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_train_fp%2C%20_test_fp%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Drop%20any%20feature%20column%20that%20contains%20a%20NaN%20in%20the%20training%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20set.%20Test%20set%20uses%20the%20same%20column%20mask%20to%20avoid%20leakage.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20np.isnan(_X_train).any()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_valid_cols%20%3D%20~np.isnan(_X_train).any(axis%3D0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_train%20%3D%20_X_train%5B%3A%2C%20_valid_cols%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_test%20%20%3D%20_X_test%5B%3A%2C%20_valid_cols%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_model_key%2C%20_ModelClass%2C%20_model_kwargs%20in%20_inproc_todo%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar_fp.set_postfix(%7B%22fold%22%3A%20_fold%2C%20%22model%22%3A%20_model_key%2C%20%22fp%22%3A%20_fp_col%7D%2C%20refresh%3DFalse)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20_ModelClass(**_model_kwargs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_model_key%20%3D%3D%20%22xgboost%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20XGBoost%20needs%20a%20val%20set%20for%20early%20stopping%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_train%2C%20_y_train%2C%20_X_test%2C%20_y_true)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_train%2C%20_y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_m.predict(_X_test)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20_model_key%2C%20%22fingerprint%22%3A%20_fp_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20f%22%7B_model_key%7D_%7B_fp_col%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_X_train%2C%20_X_test%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_checkpoint(_all_records)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Pass%202%20(%7B_fp_col%7D)%20done%20%E2%80%94%20%7Blen(_all_records)%3A%2C%7D%20records%20so%20far%22)%0A%0A%20%20%20%20%20%20%20%20%23%20All%20passes%20complete%20%E2%80%94%20write%20final%20file%20and%20clean%20up%20checkpoint%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_all_records).write_csv(_f)%0A%20%20%20%20%20%20%20%20_CKPT_PATH.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20print(f%22All%20passes%20done%20%E2%80%94%20%7Blen(_all_records)%3A%2C%7D%20records%20total%20%5Cu2192%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20fp_cmp_pred_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20return%20(fp_cmp_pred_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20fp_cmp_pred_df%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Summarise%20the%20fingerprint%20%C3%97%20model%20comparison%20with%20a%20mean%20metrics%20table%0A%20%20%20%20and%20MCS%20heatmaps%20(Tukey%20HSD)%20for%20MAE%2C%20R%C2%B2%20and%20%CF%81.%0A%20%20%20%20%22%22%22%0A%20%20%20%20%23%20Add%20CheMeleon%20fine-tuned%20baseline%20from%20notebook%202%20for%20comparison%0A%20%20%20%20_baseline_chemeleon%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%2C%20%22model%22%3A%20%22method%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22chemeleon_base%22).alias(%22method%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22random%22).alias(%22split%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20_combined%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20fp_cmp_pred_df%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20_baseline_chemeleon%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics_df%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_combined%2C%0A%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics_df%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22%2C%20descending%3DFalse)%0A%20%20%20%20)%0A%0A%20%20%20%20_ABBREV%20%3D%20%7B%22xgboost%22%3A%20%22xg%22%2C%20%22chemeleon_base%22%3A%20%22che_b%22%2C%20%22chemeleon%22%3A%20%22che%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mordred%22%3A%20%22mor%22%2C%20%22chemprop%22%3A%20%22cp%22%2C%20%22no_pretrain%22%3A%20%22bas%22%2C%20%22macau%22%3A%20%22mc%22%7D%0A%20%20%20%20def%20_shorten(name%3A%20str)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20for%20long%2C%20short%20in%20_ABBREV.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%3D%20name.replace(long%2C%20short)%0A%20%20%20%20%20%20%20%20return%20name%0A%0A%20%20%20%20_metrics_plot%20%3D%20_metrics_df.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22method%22).map_elements(_shorten%2C%20return_dtype%3Dpl.String)%0A%20%20%20%20)%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics_plot%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(10%2C%2010)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.1%2C%20%22mse%22%3A%200.2%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR%20%2F%20%22analysis1_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%201%20%E2%80%94%20RF%20%2F%20XGBoost%20%2F%20Macau%20%C3%97%20fingerprint%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22---%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Pretraining%20experiment%0A%0A%20%20%20%20%23%23%20Hypothesis%0A%20%20%20%20Pretraining%20a%20Chemprop%20D-MPNN%20(or%20CheMeleon%20fine-tune)%20on%20a%20related%20but%0A%20%20%20%20distinct%20biological%20activity%20before%20fine-tuning%20on%20the%20dose-response%20pEC50%0A%20%20%20%20target%20may%20improve%20generalisation%2C%20particularly%20for%20the%20smaller%20dose-response%0A%20%20%20%20training%20set.%0A%0A%20%20%20%20%23%23%20Pretrain%20datasets%0A%0A%20%20%20%20%7C%20Name%20%7C%20Columns%20used%20%7C%20Task%20%7C%20Compounds%20%7C%20Notes%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60sd10_reg%60%20%7C%20%6010.0_log2_fc%60%20%7C%20regression%20%7C%20~10%20747%20%7C%2010%20%C2%B5M%20single-dose%3B%20includes%20DR-overlap%20compounds%20%7C%0A%20%20%20%20%7C%20%60sd30_reg%60%20%7C%20%6030.0_log2_fc%60%20%7C%20regression%20%7C%20~9%20523%20%7C%2030%20%C2%B5M%20single-dose%3B%20includes%20DR-overlap%20compounds%20%7C%0A%20%20%20%20%7C%20%60sd10_cls%60%20%7C%20%6010.0_is_hit%60%20%7C%20classification%20%7C%20~10%20747%20%7C%20Same%20as%20sd10_reg%20but%20predicting%20hit%2Fnon-hit%20%7C%0A%20%20%20%20%7C%20%60sd30_cls%60%20%7C%20%6030.0_is_hit%60%20%7C%20classification%20%7C%20~9%20523%20%7C%20Same%20as%20sd30_reg%20but%20predicting%20hit%2Fnon-hit%20%7C%0A%20%20%20%20%7C%20%60counter_ic50%60%20%7C%20%60pEC50_counter%60%20%7C%20regression%20%7C%202%20646%20%7C%20All%20counter%20compounds%20are%20also%20in%20DR%20%E2%80%94%20mild%20label-leakage%20risk%20%7C%0A%0A%20%20%20%20Single-dose%20sets%20include%20all%20available%20compounds%20(including%20those%20with%20dose-response%0A%20%20%20%20data)%2C%20accepting%20the%20mild%20leakage%20in%20exchange%20for%20a%20larger%20and%20more%20representative%0A%20%20%20%20pretrain%20corpus.%0A%0A%20%20%20%20Counter-screen%20compounds%20are%20a%20strict%20subset%20of%20the%20dose-response%20set%2C%20so%0A%20%20%20%20their%20pEC50%20values%20will%20have%20been%20seen%20during%20pretraining%20for%20some%20test-fold%0A%20%20%20%20compounds%20%E2%80%94%20treat%20results%20with%20caution.%0A%0A%20%20%20%20%23%23%20Models%20tested%0A%0A%20%20%20%20%60ChempropModel%60%20and%20%60ChempropChemeleonModel%60%2C%20each%20with%3A%0A%0A%20%20%20%20-%20**no_pretrain**%20%E2%80%94%20baseline%20(scratch%20%2F%20CheMeleon%20only)%0A%20%20%20%20-%20**sd10_reg**%20%E2%80%94%20pretrained%20on%2010%20%C2%B5M%20log2%20fold-change%20(regression)%0A%20%20%20%20-%20**sd30_reg**%20%E2%80%94%20pretrained%20on%2030%20%C2%B5M%20log2%20fold-change%20(regression)%0A%20%20%20%20-%20**sd10_cls**%20%E2%80%94%20pretrained%20on%2010%20%C2%B5M%20hit%20classification%0A%20%20%20%20-%20**sd30_cls**%20%E2%80%94%20pretrained%20on%2030%20%C2%B5M%20hit%20classification%0A%20%20%20%20-%20**counter_ic50**%20%E2%80%94%20pretrained%20on%20counter-screen%20pEC50%0A%0A%20%20%20%20%23%23%20CV%20protocol%0A%0A%20%20%20%20Identical%20to%20notebook%203%3A%205%C3%975%20random%20CV%2C%20seed%3D42%2C%20p_val%3D0.1%2C%0A%20%20%20%20target%20%3D%20%60pEC50_dr%60.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gc%2C%20pl)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Load%20the%20master%20activity%20table%20and%20build%20each%20pretrain%20dataset.%0A%0A%20%20%20%20Single-dose%20sets%20include%20all%20available%20compounds%20(including%20those%20that%20also%0A%20%20%20%20have%20dose-response%20data)%2C%20accepting%20mild%20label%20leakage%20for%20a%20larger%20pretrain%0A%20%20%20%20corpus.%20Counter-screen%20compounds%20are%20a%20strict%20subset%20of%20the%20dose-response%20set%0A%20%20%20%20and%20are%20included%20with%20that%20leakage%20caveat%20noted.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_all%20%3D%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20fine-tuning%20target%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20pretrain_dr_train%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22molecule_names%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20pretrain%20sets%20(single-dose%20regression%20%E2%80%94%20all%20compounds%20with%20measurement)%20%E2%94%80%0A%20%20%20%20pretrain_sd10_reg%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%2210.0_log2_fc%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%2210.0_log2_fc%22%5D)%0A%20%20%20%20%20%20%20%20.rename(%7B%2210.0_log2_fc%22%3A%20%22log2_fc%22%7D)%0A%20%20%20%20)%0A%0A%20%20%20%20pretrain_sd30_reg%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%2230.0_log2_fc%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%2230.0_log2_fc%22%5D)%0A%20%20%20%20%20%20%20%20.rename(%7B%2230.0_log2_fc%22%3A%20%22log2_fc%22%7D)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20pretrain%20sets%20(single-dose%20classification%20%E2%80%94%20hit%2Fnon-hit)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Cast%20boolean%20is_hit%20to%20integer%20(0%2F1)%20for%20the%20Chemprop%20binary%20classifier.%0A%20%20%20%20pretrain_sd10_cls%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%2210.0_is_hit%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%2210.0_is_hit%22%5D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%2210.0_is_hit%22).cast(pl.Int8).alias(%22is_hit%22))%0A%20%20%20%20%20%20%20%20.drop(%2210.0_is_hit%22)%0A%20%20%20%20)%0A%0A%20%20%20%20pretrain_sd30_cls%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%2230.0_is_hit%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%2230.0_is_hit%22%5D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.col(%2230.0_is_hit%22).cast(pl.Int8).alias(%22is_hit%22))%0A%20%20%20%20%20%20%20%20.drop(%2230.0_is_hit%22)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20pretrain%20set%20(counter%20screen%20%E2%80%94%20overlaps%20with%20DR%2C%20leakage%20caveat)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20pretrain_counter_ic50%20%3D%20(%0A%20%20%20%20%20%20%20%20_all%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_counter%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22pEC50_counter%22%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20del%20_all%0A%20%20%20%20gc.collect()%0A%0A%20%20%20%20print(f%22DR%20train%20set%3A%20%20%20%20%20%20%20%20%20%20%20%20%7Blen(pretrain_dr_train)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20print(f%22Pretrain%20SD10%20reg%3A%20%20%20%20%20%20%20%7Blen(pretrain_sd10_reg)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20print(f%22Pretrain%20SD30%20reg%3A%20%20%20%20%20%20%20%7Blen(pretrain_sd30_reg)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20print(f%22Pretrain%20SD10%20cls%3A%20%20%20%20%20%20%20%7Blen(pretrain_sd10_cls)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20print(f%22Pretrain%20SD30%20cls%3A%20%20%20%20%20%20%20%7Blen(pretrain_sd30_cls)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20print(f%22Pretrain%20counter%20IC50%3A%20%20%20%7Blen(pretrain_counter_ic50)%3A%3E6%7D%20compounds%22)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20pretrain_counter_ic50%2C%0A%20%20%20%20%20%20%20%20pretrain_dr_train%2C%0A%20%20%20%20%20%20%20%20pretrain_sd10_cls%2C%0A%20%20%20%20%20%20%20%20pretrain_sd10_reg%2C%0A%20%20%20%20%20%20%20%20pretrain_sd30_cls%2C%0A%20%20%20%20%20%20%20%20pretrain_sd30_reg%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20pretrain_counter_ic50%2C%0A%20%20%20%20pretrain_dr_train%2C%0A%20%20%20%20pretrain_sd10_cls%2C%0A%20%20%20%20pretrain_sd10_reg%2C%0A%20%20%20%20pretrain_sd30_cls%2C%0A%20%20%20%20pretrain_sd30_reg%2C%0A%20%20%20%20split_dataset_random%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%205%C3%975%20CV%20pretraining%20experiment.%0A%0A%20%20%20%20For%20each%20model%20class%20%C3%97%20pretrain%20strategy%3A%0A%20%20%20%20%20%201.%20(Optional)%20pretrain%20on%20the%20auxiliary%20dataset%20using%20the%20pretrain%20task's%0A%20%20%20%20%20%20%20%20%20pred_type%20(regression%20for%20log2_fc%20%2F%20pEC50%3B%20classification%20for%20is_hit).%0A%20%20%20%20%20%202.%20Fine-tune%20%2F%20train%20on%20each%20CV%20fold's%20training%20split%20(always%20regression).%0A%20%20%20%20%20%203.%20Predict%20on%20the%20held-out%20test%20split.%0A%0A%20%20%20%20Seeds%2C%20n_outer%2C%20n_inner%2C%20and%20p_val%20match%20notebook%203%20exactly%20so%20the%0A%20%20%20%20no_pretrain%20baselines%20are%20directly%20comparable.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F4_pretrain_experiment.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%20%20%20%23%20fraction%20of%20train%20kept%20as%20val%20for%20early%20stopping%0A%0A%20%20%20%20%23%20Mapping%3A%20pretrain_name%20%E2%86%92%20(pretrain_df%2C%20pretrain_target_col%2C%20pretrain_pred_type)%0A%20%20%20%20%23%20pretrain_pred_type%20controls%20the%20task%20used%20during%20pretraining%20only%3B%0A%20%20%20%20%23%20fine-tuning%20is%20always%20regression%20on%20pEC50_dr.%0A%20%20%20%20_PRETRAIN_SETS%3A%20dict%5Bstr%2C%20tuple%20%7C%20None%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22no_pretrain%22%3A%20%20None%2C%0A%20%20%20%20%20%20%20%20%22sd10_reg%22%3A%20%20%20%20%20(pretrain_sd10_reg%2C%20%22log2_fc%22%2C%20%20%20%20%20%20%20%22regression%22)%2C%0A%20%20%20%20%20%20%20%20%22sd30_reg%22%3A%20%20%20%20%20(pretrain_sd30_reg%2C%20%22log2_fc%22%2C%20%20%20%20%20%20%20%22regression%22)%2C%0A%20%20%20%20%20%20%20%20%22sd10_cls%22%3A%20%20%20%20%20(pretrain_sd10_cls%2C%20%22is_hit%22%2C%20%20%20%20%20%20%20%20%22classification%22)%2C%0A%20%20%20%20%20%20%20%20%22sd30_cls%22%3A%20%20%20%20%20(pretrain_sd30_cls%2C%20%22is_hit%22%2C%20%20%20%20%20%20%20%20%22classification%22)%2C%0A%20%20%20%20%20%20%20%20%22counter_ic50%22%3A%20(pretrain_counter_ic50%2C%20%22pEC50_counter%22%2C%20%22regression%22)%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20_MODEL_CLASSES%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22chemprop%22%3A%20ChempropModel%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20_EXPECTED_PRETRAIN_METHODS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20f%22%7Bmk%7D_%7Bpn%7D%22%20for%20mk%20in%20_MODEL_CLASSES%20for%20pn%20in%20_PRETRAIN_SETS%0A%20%20%20%20%7D%0A%20%20%20%20_is_complete%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.exists()%0A%20%20%20%20%20%20%20%20and%20set(pl.read_csv(_PRED_PATH_GZ)%5B%22method%22%5D.unique().to_list())%20%3E%3D%20_EXPECTED_PRETRAIN_METHODS%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_complete%3A%0A%20%20%20%20%20%20%20%20print(f%22Complete%20predictions%20found%20at%20%7B_PRED_PATH_GZ%7D%20%E2%80%94%20skipping%20training.%22)%0A%20%20%20%20%20%20%20%20pred_df_pretrain%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20if%20_PRED_PATH_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(%22Partial%2Fincomplete%20file%20found%20%E2%80%94%20removing.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20_PRED_PATH_GZ.unlink()%0A%20%20%20%20%20%20%20%20_CKPT_PATH%20%3D%20_PRED_PATH_GZ.with_suffix(%22.ckpt.gz%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Resume%20from%20checkpoint%20if%20one%20exists.%0A%20%20%20%20%20%20%20%20%23%20_done_methods%20%20%E2%80%94%20methods%20with%20all%2025%20folds%20complete%20(skip%20entirely).%0A%20%20%20%20%20%20%20%20%23%20_done_folds%20%20%20%20%E2%80%94%20method%20%E2%86%92%20set%20of%20fold%20indices%20already%20saved%20(partial%20resume).%0A%20%20%20%20%20%20%20%20_n_folds_total%20%3D%20_N_OUTER%20*%20_N_INNER%0A%20%20%20%20%20%20%20%20if%20_CKPT_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df%20%3D%20pl.read_csv(_CKPT_PATH)%0A%20%20%20%20%20%20%20%20%20%20%20%20_fold_counts%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%22fold%22).n_unique().alias(%22n_folds%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_methods%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r%5B%22method%22%5D%20for%20r%20in%20_fold_counts.to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20r%5B%22n_folds%22%5D%20%3E%3D%20_n_folds_total%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_folds%3A%20dict%5Bstr%2C%20set%5Bint%5D%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20r%5B%22method%22%5D%3A%20set(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df.filter(pl.col(%22method%22)%20%3D%3D%20r%5B%22method%22%5D)%5B%22fold%22%5D.unique().to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20r%20in%20_fold_counts.to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20r%5B%22n_folds%22%5D%20%3C%20_n_folds_total%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Only%20keep%20fully-complete%20methods%20in%20_all_records%20to%20avoid%20duplication%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20when%20partial%20strategies%20re-add%20their%20rows%20via%20_strategy_records.%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df.filter(pl.col(%22method%22).is_in(list(_done_methods))).to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20_partial%20%3D%20%7Bm%3A%20len(f)%20for%20m%2C%20f%20in%20_done_folds.items()%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Resuming%20from%20checkpoint%3A%20%7Blen(_ckpt_df)%3A%2C%7D%20rows%20total%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20Fully%20complete%3A%20%7Bsorted(_done_methods)%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20Partial%3A%20%20%20%20%20%20%20%20%7B_partial%7D%22)%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df%20%3D%20pl.DataFrame()%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_methods%3A%20set%5Bstr%5D%20%3D%20set()%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_folds%3A%20dict%5Bstr%2C%20set%5Bint%5D%5D%20%3D%20%7B%7D%0A%0A%20%20%20%20%20%20%20%20def%20_checkpoint(records%3A%20list%5Bdict%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22Append%20records%20to%20the%20checkpoint%2C%20merging%20with%20any%20existing%20data.%22%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20records%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20gzip.open(_CKPT_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(records).write_csv(_f)%0A%0A%20%20%20%20%20%20%20%20for%20_model_key%2C%20_ModelClass%20in%20_MODEL_CLASSES.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_pretrain_name%2C%20_pretrain_spec%20in%20_PRETRAIN_SETS.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_method_key%20%3D%20f%22%7B_model_key%7D_%7B_pretrain_name%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_method_key%20in%20_done_methods%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Skipping%20%7B_method_key%7D%20%E2%80%94%20already%20in%20checkpoint.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%5Cn%7B'%3D'*60%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Model%3A%20%7B_model_key%7D%20%20%7C%20%20Pretrain%3A%20%7B_pretrain_name%7D%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B'%3D'*60%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Seed%20strategy%20records%20from%20checkpoint%20for%20partial%20resume%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_completed_folds%20%3D%20_done_folds.get(_method_key%2C%20set())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Load%20partial%20folds%20from%20per-strategy%20checkpoint%20if%20it%20exists%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20otherwise%20fall%20back%20to%20the%20main%20checkpoint.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_ckpt%20%3D%20_CKPT_PATH.with_name(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.stem.replace(%22.csv%22%2C%20%22%22)%20%2B%20f%22_%7B_method_key%7D.csv.gz%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_strategy_ckpt.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_records%20%3D%20pl.read_csv(_strategy_ckpt).to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_completed_folds%20%3D%20%7Br%5B%22fold%22%5D%20for%20r%20in%20_strategy_records%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20Resuming%20from%20per-strategy%20checkpoint%20%E2%80%94%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%7Blen(_completed_folds)%7D%2F25%20folds%20done.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20_completed_folds%20and%20len(_ckpt_df)%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_records%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ckpt_df.filter(pl.col(%22method%22)%20%3D%3D%20_method_key).to_dicts()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_completed_folds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20Resuming%20%E2%80%94%20%7Blen(_completed_folds)%7D%2F25%20folds%20already%20done.%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20step%201%3A%20pretrain%20once%20before%20the%20CV%20loop%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Skip%20if%20resuming%20a%20partial%20run%20%E2%80%94%20the%20pretrain%20checkpoint%20already%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20exists%20on%20disk%20and%20train()%20will%20find%20it%20via%20pretrain_dir.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_pretrain_spec%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pretrain_df%2C%20_pretrain_col%2C%20_pretrain_type%20%3D%20_pretrain_spec%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Use%20a%20temporary%20instance%20just%20to%20resolve%20the%20pretrain_dir%20path%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_tmp_inst%20%3D%20_ModelClass(pred_type%3D_pretrain_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_shared_pretrain_dir%20%3D%20_tmp_inst.pretrain_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_tmp_inst%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_completed_folds%20and%20_shared_pretrain_dir.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Pretrain%20checkpoint%20found%20at%20%7B_shared_pretrain_dir%7D%20%E2%80%94%20skipping%20pretrain.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pt_train%2C%20_pt_val%20%3D%20split_dataset_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pretrain_df%2C%20p_test%3D0.1%2C%20seed%3D_SEED%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model_inst%20%3D%20_ModelClass(pred_type%3D_pretrain_type)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Pretraining%20on%20%7Blen(_pretrain_df)%7D%20compounds%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22(%7B_pretrain_col%7D%2C%20%7B_pretrain_type%7D)%E2%80%A6%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model_inst.pretrain(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pt_train%5B%22smiles%22%5D.to_list()%2C%20_pt_train%5B_pretrain_col%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pt_val%5B%22smiles%22%5D.to_list()%2C%20%20%20_pt_val%5B_pretrain_col%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_pretrain_col%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_shared_pretrain_dir%20%3D%20_model_inst.pretrain_dir%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model_inst%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_shared_pretrain_dir%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E2%94%80%E2%94%80%20step%202%3A%205%C3%975%20CV%20fine-tuning%20loop%20(always%20regression)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_n_folds%20%3D%20_N_OUTER%20*%20_N_INNER%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_remaining_folds%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20t%20for%20t%20in%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dr_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20t%5B0%5D%20not%20in%20_completed_folds%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pbar%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_remaining_folds%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_n_folds%20-%20len(_completed_folds)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc%3Df%22%7B_model_key%7D%2F%7B_pretrain_name%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_train%20%3D%20_train_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_val%20%20%20%3D%20_val_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_test%20%20%3D%20_test_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_train%20%20%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_val%20%20%20%20%20%3D%20_val_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%20%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20New%20instance%20per%20fold%3B%20fine-tuning%20is%20always%20regression.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Passing%20pretrain_dir%20makes%20train()%20load%20the%20pretrained%20encoder.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_shared_pretrain_dir%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fold_model%20%3D%20_ModelClass(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pred_type%3D%22regression%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dir%3D_shared_pretrain_dir%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fold_model%20%3D%20_ModelClass(pred_type%3D%22regression%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fold_model.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_train%2C%20_y_train%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_val%2C%20%20%20_y_val%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred%20%3D%20_fold_model.predict(_smi_test)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_fold_model%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_smi_test%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20%20%20%20%20%20%20_ik%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22molecule_names%22%3A%20_mn%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22smiles%22%3A%20%20%20%20%20%20%20%20%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20%20%20%20%20%20%20%20%20%20%20_fold%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22outer_fold%22%3A%20%20%20%20%20_outer%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inner_fold%22%3A%20%20%20%20%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20%20%20%20%20%20%20%20%20%20_model_key%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22pretrain%22%3A%20%20%20%20%20%20%20_pretrain_name%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20%20%20%20%20%20%20%20%20f%22%7B_model_key%7D_%7B_pretrain_name%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20%20%20%20%20%20%20%20%20_yt%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_pred%22%3A%20%20%20%20%20%20%20%20%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Write%20only%20this%20strategy's%20rows%20to%20a%20small%20per-strategy%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20checkpoint%20%E2%80%94%20avoids%20re-serialising%20the%20entire%20_all_records%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20list%20(which%20grows%20with%20each%20completed%20strategy)%20on%20every%20fold.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_ckpt%20%3D%20_CKPT_PATH.with_name(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.stem.replace(%22.csv%22%2C%20%22%22)%20%2B%20f%22_%7B_method_key%7D.csv.gz%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_ckpt.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20with%20gzip.open(_strategy_ckpt%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_strategy_records).write_csv(_f)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.extend(_strategy_records)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Merge%20into%20the%20main%20checkpoint%20and%20remove%20the%20per-strategy%20file%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_checkpoint(_all_records)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_ckpt%20%3D%20_CKPT_PATH.with_name(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.stem.replace(%22.csv%22%2C%20%22%22)%20%2B%20f%22_%7B_method_key%7D.csv.gz%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_strategy_ckpt.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%20%20%7B_method_key%7D%20done%20%E2%80%94%20%7Blen(_all_records)%3A%2C%7D%20records%20so%20far%22)%0A%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_all_records).write_csv(_f)%0A%20%20%20%20%20%20%20%20_CKPT_PATH.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20print(f%22%5CnSaved%20%7Blen(_all_records)%3A%2C%7D%20prediction%20rows%20%E2%86%92%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20pred_df_pretrain%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20return%20(pred_df_pretrain%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20pred_df_pretrain%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Compute%20per-fold%20metrics%20and%20display%20a%20summary%20table%20%2B%20MCS%20heatmaps.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_metrics_df%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20pred_df_pretrain%0A%20%20%20%20%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics_df%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22%2C%20descending%3DFalse)%0A%20%20%20%20)%0A%0A%20%20%20%20_ABBREV%20%3D%20%7B%22xgboost%22%3A%20%22xg%22%2C%20%22chemeleon_base%22%3A%20%22che_b%22%2C%20%22chemeleon%22%3A%20%22che%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mordred%22%3A%20%22mor%22%2C%20%22chemprop%22%3A%20%22cp%22%2C%20%22no_pretrain%22%3A%20%22bas%22%2C%20%22macau%22%3A%20%22mc%22%7D%0A%20%20%20%20def%20_shorten(name%3A%20str)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20for%20long%2C%20short%20in%20_ABBREV.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20name%20%3D%20name.replace(long%2C%20short)%0A%20%20%20%20%20%20%20%20return%20name%0A%0A%20%20%20%20_metrics_plot%20%3D%20_metrics_df.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22method%22).map_elements(_shorten%2C%20return_dtype%3Dpl.String)%0A%20%20%20%20)%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics_plot%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(10%2C%2010)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.2%2C%20%22mse%22%3A%200.2%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR%20%2F%20%22analysis2_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%202%20%E2%80%94%20Chemprop%20pretraining%20experiment%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22---%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Chemprop%20hyperparameter%20optimisation%0A%0A%20%20%20%20%23%23%20Parameter%20space%0A%0A%20%20%20%20CheMeleon%20fixes%20the%20MPNN%20backbone%20(%60d_h%3D2048%60%2C%20%60depth%3D6%60%2C%20%60dropout%3D0.0%60%2C%0A%20%20%20%20%60activation%3Drelu%60)%2C%20so%20those%20are%20excluded%20from%20the%20HPO%20on%20the%20scratch%0A%20%20%20%20%60ChempropModel%60.%20%20The%20free%20parameters%20are%3A%0A%0A%20%20%20%20%7C%20Parameter%20%7C%20Role%20%7C%20Default%20%7C%20Sensitivity%20scan%20%7C%20HPO%20%7C%0A%20%20%20%20%7C---%7C---%7C---%7C---%7C---%7C%0A%20%20%20%20%7C%20%60message_hidden_dim%60%20%7C%20MPNN%20hidden%20size%20%7C%20300%20%7C%20%E2%9C%93%20(timing%20impact)%20%7C%20%E2%9C%97%20fixed%20by%20CheMeleon%20%7C%0A%20%20%20%20%7C%20%60depth%60%20%7C%20message-passing%20steps%20%7C%203%20%7C%20%E2%9C%93%20(timing%20impact)%20%7C%20%E2%9C%97%20fixed%20by%20CheMeleon%20%7C%0A%20%20%20%20%7C%20%60dropout%60%20%7C%20regularisation%20%7C%200.0%20%7C%20%E2%9C%93%20%7C%20%E2%9C%93%200.0%20%E2%80%93%200.4%20%7C%0A%20%20%20%20%7C%20%60ffn_hidden_dim%60%20%7C%20FFN%20hidden%20size%20%7C%20300%20%7C%20%E2%9C%93%20%7C%20%E2%9C%93%20128%20%E2%80%93%201024%20%7C%0A%20%20%20%20%7C%20%60ffn_num_layers%60%20%7C%20FFN%20depth%20%7C%202%20%7C%20%E2%9C%93%20%7C%20%E2%9C%93%201%20%E2%80%93%204%20%7C%0A%20%20%20%20%7C%20%60max_lr%60%20%7C%20peak%20learning%20rate%20%7C%201e-3%20%7C%20%E2%9C%93%20%7C%20%E2%9C%93%201e-4%20%E2%80%93%201e-2%20%7C%0A%20%20%20%20%7C%20%60batch_size%60%20%7C%20mini-batch%20size%20%7C%2064%20%7C%20%E2%9C%93%20%7C%20%E2%9C%93%2032%2C%2064%2C%20128%20%7C%0A%0A%20%20%20%20%23%23%20Protocol%0A%0A%20%20%20%20**Step%201%20%E2%80%94%20sensitivity%20scan**%3A%20train%20one%20model%20per%20parameter%20value%20on%20an%0A%20%20%20%2080%2F20%20random%20split%20to%20understand%20the%20individual%20effect%20of%20each%20parameter%0A%20%20%20%20before%20running%20the%20full%20HPO.%0A%0A%20%20%20%20**Step%202%20%E2%80%94%20Optuna%20TPE%20HPO**%3A%2050%20trials%20using%201%C3%975%20CV%20on%20the%20full%20DR%20dataset%2C%0A%20%20%20%20persisted%20to%20an%20SQLite%20database%20so%20the%20study%20can%20be%20resumed%20across%20sessions.%0A%20%20%20%20Objective%3A%20mean%20MAE%20across%20all%205%20folds%20(predictions%20cover%20the%20full%20dataset).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gc%2C%20pl%2C%20split_dataset_random)%3A%0A%20%20%20%20%22%22%22Load%20the%20DR%20dataset%20and%20create%20a%20fixed%2080%2F20%20train%2Ftest%20split%20for%20HPO.%22%22%22%0A%20%20%20%20_all_dr%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%20%20%20%20hpo_train%2C%20hpo_test%20%3D%20split_dataset_random(_all_dr%2C%20p_test%3D0.2%2C%20seed%3D42)%0A%20%20%20%20gc.collect()%0A%20%20%20%20print(f%22HPO%20train%3A%20%7Blen(hpo_train)%7D%20%20%7C%20%20HPO%20test%3A%20%7Blen(hpo_test)%7D%22)%0A%20%20%20%20return%20hpo_test%2C%20hpo_train%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20hpo_test%2C%0A%20%20%20%20hpo_train%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20plt%2C%0A%20%20%20%20spearmanr%2C%0A%20%20%20%20warnings%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Parameter%20sensitivity%20scan.%0A%0A%20%20%20%20For%20each%20parameter%2C%20vary%20it%20across%20its%20candidate%20values%20while%20keeping%20all%0A%20%20%20%20others%20at%20their%20defaults.%20%20Each%20configuration%20is%20trained%20once%20on%20the%2080%2F20%0A%20%20%20%20split.%20%20Training%20wall-clock%20time%2C%20MAE%20and%20Spearman%20%CF%81%20are%20recorded.%0A%20%20%20%20Results%20are%20saved%20to%20predictions%2F4_hpo_sensitivity.csv.gz.%0A%0A%20%20%20%20message_hidden_dim%20and%20depth%20are%20included%20here%20because%20they%20have%20a%20large%0A%20%20%20%20impact%20on%20training%20time%20%E2%80%94%20important%20context%20even%20though%20they%20are%20excluded%0A%20%20%20%20from%20the%20HPO%20(CheMeleon%20fixes%20them).%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20time%20as%20_time%0A%0A%20%20%20%20_SENSITIVITY_PATH%20%3D%20Path(%22..%2Fpredictions%2F4_hpo_sensitivity.csv.gz%22)%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%0A%20%20%20%20_DEFAULTS%20%3D%20dict(%0A%20%20%20%20%20%20%20%20message_hidden_dim%3D300%2C%0A%20%20%20%20%20%20%20%20depth%3D3%2C%0A%20%20%20%20%20%20%20%20dropout%3D0.0%2C%0A%20%20%20%20%20%20%20%20ffn_hidden_dim%3D300%2C%0A%20%20%20%20%20%20%20%20ffn_num_layers%3D2%2C%0A%20%20%20%20%20%20%20%20max_lr%3D1e-3%2C%0A%20%20%20%20%20%20%20%20batch_size%3D64%2C%0A%20%20%20%20%20%20%20%20epochs%3D50%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_PARAM_GRID%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22message_hidden_dim%22%3A%20%5B128%2C%20300%2C%20512%2C%201024%2C%202048%5D%2C%0A%20%20%20%20%20%20%20%20%22depth%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B2%2C%203%2C%204%2C%205%2C%206%5D%2C%0A%20%20%20%20%20%20%20%20%22dropout%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%5B0.0%2C%200.1%2C%200.2%2C%200.3%2C%200.4%5D%2C%0A%20%20%20%20%20%20%20%20%22ffn_hidden_dim%22%3A%20%20%20%20%20%5B128%2C%20300%2C%20512%2C%201024%2C%202048%5D%2C%0A%20%20%20%20%20%20%20%20%22ffn_num_layers%22%3A%20%20%20%20%20%5B1%2C%202%2C%203%2C%204%5D%2C%0A%20%20%20%20%20%20%20%20%22max_lr%22%3A%20%20%20%20%20%20%20%20%20%20%20%20%20%5B1e-4%2C%205e-4%2C%201e-3%2C%203e-3%2C%201e-2%5D%2C%0A%20%20%20%20%20%20%20%20%22batch_size%22%3A%20%20%20%20%20%20%20%20%20%5B32%2C%2064%2C%20128%5D%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20_SENSITIVITY_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22Sensitivity%20results%20found%20%E2%80%94%20loading.%22)%0A%20%20%20%20%20%20%20%20_sens_df%20%3D%20pl.read_csv(_SENSITIVITY_PATH)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_smi_train%20%3D%20hpo_train%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20_smi_test%20%20%3D%20hpo_test%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20_y_train%20%20%20%3D%20hpo_train%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20_y_test%20%20%20%20%3D%20hpo_test%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20_n_val%20%20%20%3D%20max(1%2C%20int(len(_smi_train)%20*%200.1))%0A%20%20%20%20%20%20%20%20_smi_val%20%3D%20_smi_train%5B-_n_val%3A%5D%0A%20%20%20%20%20%20%20%20_y_val%20%20%20%3D%20_y_train%5B-_n_val%3A%5D%0A%20%20%20%20%20%20%20%20_smi_tr%20%20%3D%20_smi_train%5B%3A-_n_val%5D%0A%20%20%20%20%20%20%20%20_y_tr%20%20%20%20%3D%20_y_train%5B%3A-_n_val%5D%0A%0A%20%20%20%20%20%20%20%20_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_total%20%3D%20sum(len(v)%20for%20v%20in%20_PARAM_GRID.values())%0A%20%20%20%20%20%20%20%20_done%20%20%3D%200%0A%0A%20%20%20%20%20%20%20%20for%20_param%2C%20_values%20in%20_PARAM_GRID.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_val%20in%20_values%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_kwargs%20%3D%20%7B**_DEFAULTS%2C%20_param%3A%20_val%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%3D%20ChempropModel(pred_type%3D%22regression%22%2C%20**_kwargs)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_t0%20%3D%20_time.perf_counter()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model.train(_smi_tr%2C%20_y_tr%2C%20_smi_val%2C%20_y_val%2C%20target_col%3D_TARGET_COL)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_sec%20%3D%20_time.perf_counter()%20-%20_t0%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds%20%3D%20_model.predict(_smi_test)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_mae%20%3D%20float(np.abs(_preds%20-%20_y_test).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20with%20warnings.catch_warnings()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20warnings.simplefilter(%22ignore%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_rho%20%3D%20float(spearmanr(_y_test%2C%20_preds).correlation)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22param%22%3A%20%20%20%20%20%20_param%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22value%22%3A%20%20%20%20%20%20float(_val)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22train_sec%22%3A%20%20round(_train_sec%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20%20%20%20%20%20%20%20_mae%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rho%22%3A%20%20%20%20%20%20%20%20_rho%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_done%20%2B%3D%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%5B%7B_done%7D%2F%7B_total%7D%5D%20%20%7B_param%7D%3D%7B_val%7D%20%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22time%3D%7B_train_sec%3A.0f%7Ds%20%20MAE%3D%7B_mae%3A.3f%7D%20%20%CF%81%3D%7B_rho%3A.3f%7D%22)%0A%0A%20%20%20%20%20%20%20%20_sens_df%20%3D%20pl.DataFrame(_records)%0A%20%20%20%20%20%20%20%20_SENSITIVITY_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_SENSITIVITY_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_sens_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%E2%86%92%20%7B_SENSITIVITY_PATH%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Plot%3A%20one%20panel%20per%20parameter%2C%20two%20lines%20(time%20%2F%20MAE)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_params%20%3D%20list(_PARAM_GRID.keys())%0A%20%20%20%20_ncols%20%3D%202%0A%20%20%20%20_nrows%20%3D%20-(-len(_params)%20%2F%2F%20_ncols)%0A%20%20%20%20_fig_s%2C%20_axes%20%3D%20plt.subplots(_nrows%2C%20_ncols%2C%20figsize%3D(12%2C%205%20*%20_nrows))%0A%20%20%20%20_axes%20%3D%20_axes.flatten()%0A%0A%20%20%20%20for%20_i%2C%20_param%20in%20enumerate(_params)%3A%0A%20%20%20%20%20%20%20%20_sub%20%20%3D%20_sens_df.filter(pl.col(%22param%22)%20%3D%3D%20_param).sort(%22value%22).to_pandas()%0A%20%20%20%20%20%20%20%20_xticks%20%3D%20%5Bstr(float(v))%20for%20v%20in%20_PARAM_GRID%5B_param%5D%5D%0A%20%20%20%20%20%20%20%20_xpos%20%20%20%3D%20list(range(len(_xticks)))%0A%0A%20%20%20%20%20%20%20%20_ax%20%20%3D%20_axes%5B_i%5D%0A%20%20%20%20%20%20%20%20_ax2%20%3D%20_ax.twinx()%0A%0A%20%20%20%20%20%20%20%20_ax.plot(_xpos%2C%20%20_sub%5B%22train_sec%22%5D%2C%20marker%3D%22D%22%2C%20color%3D%22tab%3Agreen%22%2C%20label%3D%22time%20(s)%22)%0A%20%20%20%20%20%20%20%20_ax2.plot(_xpos%2C%20_sub%5B%22mae%22%5D%2C%20%20%20%20%20%20%20marker%3D%22o%22%2C%20color%3D%22tab%3Ablue%22%2C%20%20label%3D%22MAE%22%2C%20linestyle%3D%22--%22)%0A%0A%20%20%20%20%20%20%20%20_ax.set_xticks(_xpos)%0A%20%20%20%20%20%20%20%20_ax.set_xticklabels(_xticks%2C%20rotation%3D30%2C%20ha%3D%22right%22%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.set_title(_param%2C%20fontsize%3D11)%0A%20%20%20%20%20%20%20%20_ax.set_xlabel(%22value%22)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel(%22train%20time%20(s)%22%2C%20color%3D%22tab%3Agreen%22)%0A%20%20%20%20%20%20%20%20_ax2.set_ylabel(%22MAE%22%2C%20%20%20%20%20%20%20%20%20%20%20color%3D%22tab%3Ablue%22)%0A%20%20%20%20%20%20%20%20_ax.tick_params(axis%3D%22y%22%2C%20labelcolor%3D%22tab%3Agreen%22)%0A%20%20%20%20%20%20%20%20_ax2.tick_params(axis%3D%22y%22%2C%20labelcolor%3D%22tab%3Ablue%22)%0A%0A%20%20%20%20%20%20%20%20%23%20Mark%20the%20default%20value%0A%20%20%20%20%20%20%20%20_default_str%20%3D%20str(float(_DEFAULTS%5B_param%5D))%0A%20%20%20%20%20%20%20%20if%20_default_str%20in%20_xticks%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_ax.axvline(_xticks.index(_default_str)%2C%20color%3D%22gray%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20linestyle%3D%22%3A%22%2C%20alpha%3D0.5)%0A%0A%20%20%20%20for%20_j%20in%20range(len(_params)%2C%20len(_axes))%3A%0A%20%20%20%20%20%20%20%20_axes%5B_j%5D.set_visible(False)%0A%0A%20%20%20%20_fig_s.suptitle(%22Sensitivity%20scan%20%E2%80%94%20training%20time%20%2F%20MAE%20per%20parameter%20(80%2F20%20split)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D13)%0A%20%20%20%20_fig_s.tight_layout()%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20_fig_s.savefig(_PLOTS_DIR%20%2F%20%22sensitivity_scan.png%22%2C%20dpi%3D150%2C%20bbox_inches%3D%22tight%22)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Sensitivity%20scan%20results%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_sens_df.sort(%5B%22param%22%2C%20%22value%22%5D).to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig_s)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20np%2C%0A%20%20%20%20optuna%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20pretrain_dr_train%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Optuna%20TPE%20hyperparameter%20optimisation%20using%201%C3%975%20CV.%0A%0A%20%20%20%20Each%20trial%20runs%205-fold%20CV%20on%20the%20full%20DR%20dataset%2C%20trains%20on%204%20folds%20with%0A%20%20%20%2010%25%20of%20that%20held%20out%20for%20early%20stopping%2C%20and%20predicts%20on%20the%205th.%20%20The%0A%20%20%20%20objective%20is%20mean%20MAE%20across%20all%205%20folds%2C%20giving%20predictions%20on%20the%20full%0A%20%20%20%20dataset.%20%20The%20study%20is%20persisted%20to%20SQLite%20so%20it%20can%20be%20resumed.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_DB_PATH%20%20%20%20%3D%20Path(%22..%2Fpredictions%2F4_hpo_chemprop.db%22)%0A%20%20%20%20_STUDY_NAME%20%3D%20%22chemprop_hpo_1x5cv%22%0A%20%20%20%20_N_TRIALS%20%20%20%3D%2050%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%20%20%20%3D%200.1%20%20%20%23%20fraction%20of%20train%20held%20out%20for%20early%20stopping%20per%20fold%0A%0A%20%20%20%20def%20_objective(trial%3A%20optuna.Trial)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%23%20message_hidden_dim%20and%20depth%20excluded%20%E2%80%94%20fixed%20by%20CheMeleon%20backbone.%0A%20%20%20%20%20%20%20%20params%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20dropout%20%20%20%20%20%20%20%20%3D%20trial.suggest_float(%22dropout%22%2C%200.0%2C%200.4%2C%20step%3D0.05)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_hidden_dim%20%3D%20trial.suggest_categorical(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ffn_hidden_dim%22%2C%20%5B128%2C%20256%2C%20300%2C%20512%2C%201024%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ffn_num_layers%20%3D%20trial.suggest_int(%22ffn_num_layers%22%2C%201%2C%204)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_lr%20%20%20%20%20%20%20%20%20%3D%20trial.suggest_float(%22max_lr%22%2C%201e-4%2C%201e-2%2C%20log%3DTrue)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20batch_size%20%20%20%20%20%3D%20trial.suggest_categorical(%22batch_size%22%2C%20%5B32%2C%2064%2C%20128%5D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20epochs%20%20%20%20%20%20%20%20%20%3D%2050%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20fold_maes%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20%23%20n_outer%3D1%20%E2%86%92%20single%20pass%3B%20n_inner%3D5%20%E2%86%92%205%20folds%20covering%20full%20dataset%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dr_train%2C%20n_outer%3D1%2C%20n_inner%3D5%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_tr%20%20%3D%20_train_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_val%20%3D%20_val_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_smi_te%20%20%3D%20_test_raw%5B%22smiles%22%5D.to_list()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_tr%20%20%20%20%3D%20_train_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_val%20%20%20%3D%20_val_raw%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20_y_te%20%20%20%20%3D%20_test_raw%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20model%20%3D%20ChempropModel(pred_type%3D%22regression%22%2C%20**params)%0A%20%20%20%20%20%20%20%20%20%20%20%20model.train(_smi_tr%2C%20_y_tr%2C%20_smi_val%2C%20_y_val%2C%20target_col%3D_TARGET_COL)%0A%20%20%20%20%20%20%20%20%20%20%20%20preds%20%3D%20model.predict(_smi_te)%0A%20%20%20%20%20%20%20%20%20%20%20%20del%20model%0A%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%20%20%20%20%20%20%20%20%20%20%20%20fold_maes.append(float(np.abs(preds%20-%20_y_te).mean()))%0A%0A%20%20%20%20%20%20%20%20return%20float(np.mean(fold_maes))%0A%0A%20%20%20%20_DB_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_study%20%3D%20optuna.create_study(%0A%20%20%20%20%20%20%20%20study_name%3D_STUDY_NAME%2C%0A%20%20%20%20%20%20%20%20storage%3Df%22sqlite%3A%2F%2F%2F%7B_DB_PATH%7D%22%2C%0A%20%20%20%20%20%20%20%20load_if_exists%3DTrue%2C%0A%20%20%20%20%20%20%20%20direction%3D%22minimize%22%2C%0A%20%20%20%20%20%20%20%20sampler%3Doptuna.samplers.TPESampler(seed%3D_SEED)%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_completed%20%3D%20len(%5Bt%20for%20t%20in%20_study.trials%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20t.state%20%3D%3D%20optuna.trial.TrialState.COMPLETE%5D)%0A%20%20%20%20_remaining%20%3D%20max(0%2C%20_N_TRIALS%20-%20_completed)%0A%20%20%20%20print(f%22Study%20'%7B_STUDY_NAME%7D'%3A%20%7B_completed%7D%20completed%2C%20%7B_remaining%7D%20remaining.%22)%0A%0A%20%20%20%20if%20_remaining%20%3E%200%3A%0A%20%20%20%20%20%20%20%20_study.optimize(_objective%2C%20n_trials%3D_remaining%2C%20show_progress_bar%3DTrue)%0A%0A%20%20%20%20_best%20%3D%20_study.best_trial%0A%20%20%20%20best_params%20%3D%20%7B**_best.params%2C%20%22epochs%22%3A%2050%7D%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(f%22%23%23%20Optuna%20HPO%20%E2%80%94%20%7B_completed%20%2B%20_remaining%7D%20trials%20complete%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(f%22**Best%20trial%20%23%7B_best.number%7D%20%20%7C%20%201%C3%975%20CV%20MAE%20%3D%20%7B_best.value%3A.4f%7D**%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(%5Bbest_params%5D).to_pandas().to_string(index%3DFalse)%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%20(best_params%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20ChempropChemeleonModel%2C%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20best_params%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20pretrain_dr_train%2C%0A%20%20%20%20rm_tukey_hsd%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Full%205%C3%975%20CV%20with%20the%20HPO-optimised%20parameters%20for%20both%20ChempropModel%20and%0A%20%20%20%20ChempropChemeleonModel%2C%20saved%20to%20predictions%2F4_hpo_best_5x5cv.csv.gz.%0A%20%20%20%20Compared%20against%20base%20chemprop%20and%20chemeleon%20from%20the%20notebook-2%20baseline.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH_GZ%20%3D%20Path(%22..%2Fpredictions%2F4_hpo_best_5x5cv.csv.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%0A%0A%20%20%20%20%23%20CheMeleon%20only%20tunes%20FFN%20params%20(encoder%20is%20fixed%20by%20the%20backbone)%0A%20%20%20%20_chemeleon_params%20%3D%20%7B%0A%20%20%20%20%20%20%20%20k%3A%20v%20for%20k%2C%20v%20in%20best_params.items()%0A%20%20%20%20%20%20%20%20if%20k%20not%20in%20(%22message_hidden_dim%22%2C%20%22depth%22)%0A%20%20%20%20%7D%0A%0A%20%20%20%20_MODELS%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22chemprop_hpo%22%3A%20%20(ChempropModel%2C%20%20%20%20%20%20%20%20%20best_params)%2C%0A%20%20%20%20%20%20%20%20%22chemeleon_hpo%22%3A%20(ChempropChemeleonModel%2C%20_chemeleon_params)%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20_EXPECTED%20%3D%20set(_MODELS.keys())%0A%20%20%20%20_is_complete%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.exists()%0A%20%20%20%20%20%20%20%20and%20set(pl.read_csv(_PRED_PATH_GZ)%5B%22model%22%5D.unique().to_list())%20%3E%3D%20_EXPECTED%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_complete%3A%0A%20%20%20%20%20%20%20%20print(f%22Results%20found%20%E2%80%94%20loading.%22)%0A%20%20%20%20%20%20%20%20_hpo_df%20%3D%20pl.read_csv(_PRED_PATH_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_all_records%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_model_key%2C%20(_ModelClass%2C%20_params)%20in%20_MODELS.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_pbar%20%3D%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pretrain_dr_train%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_N_OUTER%20*%20_N_INNER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc%3Df%22CV%20%7B_model_key%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_raw%2C%20_val_raw%2C%20_test_raw%20in%20_pbar%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20_ModelClass(pred_type%3D%22regression%22%2C%20**_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_raw%5B%22smiles%22%5D.to_list()%2C%20_train_raw%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_val_raw%5B%22smiles%22%5D.to_list()%2C%20%20%20_val_raw%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds%20%3D%20_m.predict(_test_raw%5B%22smiles%22%5D.to_list())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_raw%5B_TARGET_COL%5D.to_numpy().tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20_model_key%2C%20%22method%22%3A%20_model_key%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20_hpo_df%20%3D%20pl.DataFrame(_all_records)%0A%20%20%20%20%20%20%20%20_PRED_PATH_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_hpo_df.write_csv(_f)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%E2%86%92%20%7B_PRED_PATH_GZ%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Load%20baselines%20from%20notebook%202%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_baseline%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22).is_in(%5B%22chemprop%22%2C%20%22chemeleon%22%5D))%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%7D)%0A%20%20%20%20)%0A%0A%20%20%20%20_combined%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20_hpo_df.drop(%22model%22)%2C%0A%20%20%20%20%20%20%20%20_baseline%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22).rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D).with_columns(%0A%20%20%20%20%20%20%20%20pl.lit(%22random%22).alias(%22split%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_combined%2C%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22rho%22%2C%20%22r2%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_result_tab%2C%20_df_means%2C%20_%2C%20_%20%3D%20rm_tukey_hsd(%0A%20%20%20%20%20%20%20%20_metrics%2C%20%22mae%22%2C%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20sort%3DTrue%2C%20direction_dict%3D%7B%22mae%22%3A%20%22minimize%22%7D%2C%0A%20%20%20%20)%0A%20%20%20%20_sig%20%3D%20_result_tab%5B_result_tab%5B%22p-adj%22%5D%20%3C%200.05%5D%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(8%2C%208)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.1%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR%20%2F%20%22hpo_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20HPO%20best%20params%20%E2%80%94%205%C3%975%20CV%20vs%20baselines%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Tukey%20HSD%20on%20MAE%20(significant%20pairs%2C%20p%20%3C%200.05)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sig%5B%5B%22group1%22%2C%20%22group2%22%2C%20%22meandiff%22%2C%20%22p-adj%22%5D%5D.to_string()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(_sig)%20%3E%200%20else%20%22%20%20No%20significant%20differences%22%0A%20%20%20%20%20%20%20%20)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20MCS%20grid%20%E2%80%94%20MAE%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Analysis%203%20%E2%80%94%20TabPFN%20on%20MQN%20and%20Mordred%20fingerprints%0A%0A%20%20%20%20TabPFN%20is%20evaluated%20on%20MQN%2C%20Mordred%20and%20CheMeleon%20and%20compared%20to%20the%0A%20%20%20%20corresponding%20RF%20and%20XGBoost%20results%20from%20Analysis%201%20(%604_fp_model_comparison_1.csv.gz%60).%0A%0A%20%20%20%20TabPFN%20runs%20on%20CPU%20to%20avoid%20MPS%20out-of-memory%20errors.%0A%20%20%20%20All%20comparisons%20use%20the%20same%205%C3%975%20CV%20splits%20(seed%3D42).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(gc%2C%20pl)%3A%0A%20%20%20%20%22%22%22Load%20the%20dose-response%20training%20set%20for%20analysis%203.%22%22%22%0A%20%20%20%20tabpfn_train%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22pEC50_dr%22).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22molecule_names%22%2C%20%22pEC50_dr%22%5D)%0A%20%20%20%20)%0A%20%20%20%20gc.collect()%0A%20%20%20%20return%20(tabpfn_train%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20chemeleon_embed%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20subprocess%2C%0A%20%20%20%20sys%2C%0A%20%20%20%20tabpfn_train%2C%0A%20%20%20%20tempfile%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Run%20TabPFN%20on%20MQN%2C%20Mordred%20and%20CheMeleon%20fingerprints%20using%205%C3%975%20CV.%0A%0A%20%20%20%20Predictions%20are%20checkpointed%20fold-by-fold%20and%20saved%20to%0A%20%20%20%20predictions%2F4_fp_model_comparison_2.csv.gz%20once%20both%20methods%20are%20complete.%0A%20%20%20%20TabPFN%20runs%20as%20a%20subprocess%20on%20CPU%20to%20avoid%20MPS%20out-of-memory%20errors.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL3%20%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_PRED_PATH3_GZ%20%3D%20Path(%22..%2Fpredictions%2F4_fp_model_comparison_2.csv.gz%22)%0A%20%20%20%20_CKPT3_PATH%20%20%20%20%3D%20_PRED_PATH3_GZ.with_suffix(%22.ckpt.gz%22)%0A%20%20%20%20_N_OUTER3%20%3D%205%0A%20%20%20%20_N_INNER3%20%3D%205%0A%20%20%20%20_SEED3%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL3%20%20%20%3D%200.1%0A%20%20%20%20_N_FOLDS3%20%3D%20_N_OUTER3%20*%20_N_INNER3%0A%20%20%20%20_FPS3%20%20%20%20%20%3D%20%5B%22mqn%22%2C%20%22mordred%22%2C%20%22chemeleon%22%5D%0A%20%20%20%20_EXPECTED3%20%3D%20%7Bf%22tabpfn_%7Bfp%7D%22%20for%20fp%20in%20_FPS3%7D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20TabPFN%20subprocess%20script%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20torch.set_num_threads%20uses%20all%20logical%20CPUs%20(P-cores%20%2B%20E-cores%20on%20Apple%0A%20%20%20%20%23%20Silicon)%20instead%20of%20PyTorch's%20default%20of%20P-cores%20only.%0A%20%20%20%20_SCRIPT_PATH%20%3D%20Path(tempfile.gettempdir())%20%2F%20%22tabpfn_a3.py%22%0A%20%20%20%20_SCRIPT_PATH.write_text(%22%5Cn%22.join(%5B%0A%20%20%20%20%20%20%20%20%22import%20os%2C%20sys%2C%20numpy%20as%20np%22%2C%0A%20%20%20%20%20%20%20%20%22os.environ%5B'KMP_DUPLICATE_LIB_OK'%5D%20%3D%20'TRUE'%22%2C%0A%20%20%20%20%20%20%20%20%22from%20dotenv%20import%20load_dotenv%3B%20from%20pathlib%20import%20Path%22%2C%0A%20%20%20%20%20%20%20%20%22load_dotenv(Path('.env'))%22%2C%0A%20%20%20%20%20%20%20%20%22import%20torch%22%2C%0A%20%20%20%20%20%20%20%20%22torch.set_num_threads(max(1%2C%20(os.cpu_count()%20or%201)%20-%201))%22%2C%0A%20%20%20%20%20%20%20%20%22from%20tabpfn%20import%20TabPFNRegressor%22%2C%0A%20%20%20%20%20%20%20%20%22X_train%20%3D%20np.load(sys.argv%5B1%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22y_train%20%3D%20np.load(sys.argv%5B2%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22X_test%20%20%3D%20np.load(sys.argv%5B3%5D)%22%2C%0A%20%20%20%20%20%20%20%20%22out%20%20%20%20%20%3D%20sys.argv%5B4%5D%22%2C%0A%20%20%20%20%20%20%20%20%22model%20%3D%20TabPFNRegressor(n_estimators%3D8%2C%20ignore_pretraining_limits%3DTrue%2C%20device%3D'cpu')%22%2C%0A%20%20%20%20%20%20%20%20%22model.fit(X_train%2C%20y_train)%22%2C%0A%20%20%20%20%20%20%20%20%22np.save(out%2C%20model.predict(X_test))%22%2C%0A%20%20%20%20%5D))%0A%0A%20%20%20%20def%20_tabpfn3_predict(X_train%3A%20np.ndarray%2C%20y_train%3A%20np.ndarray%2C%20X_test%3A%20np.ndarray)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20tmp%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20%20%20%20%20f_Xtr%2C%20f_ytr%2C%20f_Xte%20%3D%20tmp%20%2F%20%22a3_Xtr.npy%22%2C%20tmp%20%2F%20%22a3_ytr.npy%22%2C%20tmp%20%2F%20%22a3_Xte.npy%22%0A%20%20%20%20%20%20%20%20f_out%20%3D%20tmp%20%2F%20%22a3_preds%22%0A%20%20%20%20%20%20%20%20np.save(str(f_Xtr)%2C%20X_train)%3B%20np.save(str(f_ytr)%2C%20y_train)%3B%20np.save(str(f_Xte)%2C%20X_test)%0A%20%20%20%20%20%20%20%20res%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20str(_SCRIPT_PATH)%2C%20str(f_Xtr)%2C%20str(f_ytr)%2C%20str(f_Xte)%2C%20str(f_out)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%20cwd%3Dstr(Path(%22..%2F%22).resolve())%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20res.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22TabPFN%20subprocess%20failed%3A%5Cn%7Bres.stderr%7D%22)%0A%20%20%20%20%20%20%20%20preds%20%3D%20np.load(str(f_out)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20for%20p%20in%20%5Bf_Xtr%2C%20f_ytr%2C%20f_Xte%2C%20Path(str(f_out)%20%2B%20%22.npy%22)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20p.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20return%20preds%0A%0A%20%20%20%20def%20_checkpoint3(records%3A%20list%5Bdict%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20if%20not%20records%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20_CKPT3_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_CKPT3_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(records).write_csv(_f)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Check%20completion%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_is_complete3%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED_PATH3_GZ.exists()%0A%20%20%20%20%20%20%20%20and%20_EXPECTED3%20%3C%3D%20set(pl.read_csv(_PRED_PATH3_GZ)%5B%22method%22%5D.unique().to_list())%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_complete3%3A%0A%20%20%20%20%20%20%20%20print(f%22All%20methods%20complete%20%E2%80%94%20loading%20%7B_PRED_PATH3_GZ%7D.%22)%0A%20%20%20%20%20%20%20%20tabpfn_pred_df%20%3D%20pl.read_csv(_PRED_PATH3_GZ)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Seed%20accumulator%20from%20final%20file%20(already-done%20methods)%20or%20checkpoint%0A%20%20%20%20%20%20%20%20if%20_PRED_PATH3_GZ.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all3%3A%20list%5Bdict%5D%20%3D%20pl.read_csv(_PRED_PATH3_GZ).to_dicts()%0A%20%20%20%20%20%20%20%20elif%20_CKPT3_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all3%20%3D%20pl.read_csv(_CKPT3_PATH).to_dicts()%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all3%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Which%20methods%20and%20folds%20are%20already%20saved%0A%20%20%20%20%20%20%20%20_done_methods3%3A%20set%5Bstr%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20m%20for%20m%20in%20_EXPECTED3%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sum(1%20for%20r%20in%20_all3%20if%20r%5B%22method%22%5D%20%3D%3D%20m%20and%20r%5B%22fold%22%5D)%20%3E%3D%20_N_FOLDS3%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20_done_folds3_by_method%3A%20dict%5Bstr%2C%20set%5Bint%5D%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22tabpfn_%7Bfp%7D%22%3A%20%7Br%5B%22fold%22%5D%20for%20r%20in%20_all3%20if%20r%5B%22method%22%5D%20%3D%3D%20f%22tabpfn_%7Bfp%7D%22%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20fp%20in%20_FPS3%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20for%20_fp%20in%20_FPS3%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_method%20%3D%20f%22tabpfn_%7B_fp%7D%22%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_folds%20%3D%20_done_folds3_by_method%5B_method%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(_done_folds)%20%3E%3D%20_N_FOLDS3%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_method%7D%20already%20complete%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_done_folds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22Resuming%20%7B_method%7D%20from%20fold%20%7Blen(_done_folds)%7D%2F%7B_N_FOLDS3%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_remaining%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20t%20for%20t%20in%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tabpfn_train%2C%20n_outer%3D_N_OUTER3%2C%20n_inner%3D_N_INNER3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED3%2C%20p_val%3D_P_VAL3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20t%5B0%5D%20not%20in%20_done_folds%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold3%2C%20_outer3%2C%20_inner3%2C%20_train3%2C%20_%2C%20_test3%20in%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_remaining%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_N_FOLDS3%20-%20len(_done_folds)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc%3Df%22CV%20%7B_method%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_train3%20%20%3D%20_train3%5B_TARGET_COL3%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true3%20%20%20%3D%20_test3%5B_TARGET_COL3%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_fp%20%3D%3D%20%22chemeleon%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Xtr3%2C%20_Xte3%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train3%5B%22smiles%22%5D.to_list()%2C%20_test3%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prefix%3D%22a3_che%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_fp3%20%3D%20generate_fingerprint(_train3%2C%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_fp3%20%20%3D%20generate_fingerprint(_test3%2C%20%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Xtr3%20%3D%20extract_fp_matrix(_train_fp3%2C%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Xte3%20%3D%20extract_fp_matrix(_test_fp3%2C%20%20_fp)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_train_fp3%2C%20_test_fp3%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20np.isnan(_Xtr3).any()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_valid%20%3D%20~np.isnan(_Xtr3).any(axis%3D0)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Xtr3%2C%20_Xte3%20%3D%20_Xtr3%5B%3A%2C%20_valid%5D%2C%20_Xte3%5B%3A%2C%20_valid%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_pred3%20%3D%20_tabpfn3_predict(_Xtr3%2C%20_y_train3%2C%20_Xte3)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_Xtr3%2C%20_Xte3%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik3%2C%20_mn3%2C%20_smi3%2C%20_yt3%2C%20_yp3%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test3%5B%22inchikey%22%5D.to_list()%2C%20_test3%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test3%5B%22smiles%22%5D.to_list()%2C%20_y_true3.tolist()%2C%20_y_pred3.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all3.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik3%2C%20%22molecule_names%22%3A%20_mn3%2C%20%22smiles%22%3A%20_smi3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold3%2C%20%22outer_fold%22%3A%20_outer3%2C%20%22inner_fold%22%3A%20_inner3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20%22tabpfn%22%2C%20%22fingerprint%22%3A%20_fp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20_method%2C%20%22y_true%22%3A%20_yt3%2C%20%22y_pred%22%3A%20_yp3%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_checkpoint3(_all3)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_method%7D%20done.%22)%0A%0A%20%20%20%20%20%20%20%20_PRED_PATH3_GZ.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH3_GZ%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_all3).write_csv(_f)%0A%20%20%20%20%20%20%20%20_CKPT3_PATH.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20print(f%22Done%20%E2%80%94%20%7Blen(_all3)%3A%2C%7D%20rows%20%E2%86%92%20%7B_PRED_PATH3_GZ%7D%22)%0A%0A%20%20%20%20%20%20%20%20tabpfn_pred_df%20%3D%20pl.read_csv(_PRED_PATH3_GZ)%0A%20%20%20%20return%20(tabpfn_pred_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20tabpfn_pred_df%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Compare%20TabPFN%20against%20RF%20(Mordred%20only)%20and%20CheMeleon%20baseline%20from%20notebook%202.%0A%20%20%20%20%22%22%22%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20RF%20Mordred%20baseline%20from%20Analysis%201%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_a1_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F4_fp_model_comparison_1.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22method%22)%20%3D%3D%20%22rf_mordred%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20CheMeleon%20fine-tuned%20baseline%20from%20notebook%202%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_chemeleon_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22method%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22chemeleon%22).alias(%22method%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22random%22).alias(%22split%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20)%0A%20%20%20%20_tabpfn_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20tabpfn_pred_df%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%0A%20%20%20%20_combined3%20%3D%20pl.concat(%5B_a1_ref%2C%20_chemeleon_ref%2C%20_tabpfn_ref%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics_df3%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_combined3%2C%0A%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary3%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics_df3%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22%2C%20descending%3DFalse)%0A%20%20%20%20)%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig3%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics_df3%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(10%2C%2010)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.1%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR%20%2F%20%22analysis3_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%203%20%E2%80%94%20TabPFN%20vs%20RF%20Mordred%20and%20CheMeleon%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary3.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20MCS%20grid%20%E2%80%94%20MAE%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig3)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Analysis%204%20%E2%80%94%20Multitask%20Macau%20on%20CheMeleon%20fingerprint%0A%0A%20%20%20%20Tests%20whether%20auxiliary%20assay%20data%20improves%20Macau's%20dose-response%20predictions%0A%20%20%20%20when%20using%20CheMeleon%20embeddings%20as%20row%20side%20information.%0A%0A%20%20%20%20Four%20scenarios%20are%20compared%2C%20all%20using%20the%20same%205%C3%975%20CV%20splits%20(seed%3D42)%3A%0A%0A%20%20%20%20%7C%20Scenario%20%7C%20Train%20rows%20%7C%20Targets%20%7C%20Description%20%7C%0A%20%20%20%20%7C----------%7C-----------%7C---------%7C-------------%7C%0A%20%20%20%20%7C%20%60base%60%20%7C%204%20138%20%7C%20pEC50%5C_dr%20%7C%20DR%20compounds%20only%20%E2%80%94%20from%20Analysis%201%20(%60macau_chemeleon%60)%20%7C%0A%20%20%20%20%7C%20%60%2Bsd_cols%60%20%7C%204%20138%20%7C%20pEC50%5C_dr%20%2B%2010%20%C2%B5M%20log%E2%82%82FC%20%7C%20Adds%20single-dose%20signal%20for%20~66%20%25%20of%20DR%20compounds%20%7C%0A%20%20%20%20%7C%20%60%2Bcounter%60%20%7C%204%20138%20%7C%20pEC50%5C_dr%20%2B%20pEC50%5C_counter%20%7C%20Adds%20counter-assay%20IC50%20for%20~64%20%25%20of%20DR%20compounds%20%7C%0A%20%20%20%20%7C%20%60%2Bsd_counter%60%20%7C%204%20138%20%7C%20pEC50%5C_dr%20%2B%2010%20%C2%B5M%20log%E2%82%82FC%20%2B%20pEC50%5C_counter%20%7C%20Both%20auxiliary%20columns%2C%20DR%20compounds%20only%20%7C%0A%20%20%20%20%7C%20%60%2Bsd_rows%60%20%7C%2012%20269%20%7C%20pEC50%5C_dr%20%2B%2010%20%C2%B5M%20log%E2%82%82FC%20%7C%20Augments%20training%20matrix%20with%208%20131%20SD-only%20compounds%20(pEC50%5C_dr%20%3D%20missing)%20%7C%0A%0A%20%20%20%20**Note%20on%20%60%2Bsd_rows%60**%3A%20the%20SD-only%20compounds%20are%20added%20to%20the%20*training*%20split%0A%20%20%20%20only%3B%20they%20are%20never%20part%20of%20the%20test%20fold%2C%20so%20evaluation%20remains%20on%20DR%0A%20%20%20%20compounds%20alone%20and%20cross-fold%20comparisons%20stay%20fair.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20MacauModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20chemeleon_embed%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Multitask%20Macau%20on%20CheMeleon%20embeddings%20%E2%80%94%20four%20auxiliary-data%20scenarios.%0A%0A%20%20%20%20Y%20matrix%20is%20(n_compounds%20%C3%97%20n_targets)%2C%20represented%20as%20a%20sparse%20COO%20matrix%0A%20%20%20%20so%20that%20missing%20values%20(NaN)%20are%20simply%20absent%20and%20do%20not%20bias%20the%20MCMC.%0A%20%20%20%20Predictions%20are%20for%20pEC50_dr%20(column%200)%20only%3B%20auxiliary%20columns%20are%20used%0A%20%20%20%20only%20to%20regularise%20the%20latent%20compound%20factors%20during%20training.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20scipy.sparse%20as%20_sp%0A%0A%20%20%20%20_TARGET_COL%20%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_SD_COL%20%20%20%20%20%20%3D%20%2210.0_log2_fc%22%0A%20%20%20%20_COUNTER_COL%20%3D%20%22pEC50_counter%22%0A%20%20%20%20_PRED_PATH%20%20%20%3D%20Path(%22..%2Fpredictions%2F4_macau_multitask.csv.gz%22)%0A%20%20%20%20_CKPT_PATH%20%20%20%3D%20_PRED_PATH.with_suffix(%22.ckpt.gz%22)%0A%20%20%20%20_N_OUTER%20%3D%205%0A%20%20%20%20_N_INNER%20%3D%205%0A%20%20%20%20_SEED%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%3D%200.1%0A%20%20%20%20_N_FOLDS%20%3D%20_N_OUTER%20*%20_N_INNER%0A%0A%20%20%20%20_SCENARIOS%20%3D%20%5B%22%2Bsd_cols%22%2C%20%22%2Bcounter%22%2C%20%22%2Bsd_counter%22%2C%20%22%2Bsd_rows%22%5D%0A%20%20%20%20_EXPECTED%20%20%3D%20set(_SCENARIOS)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Load%20full%20activity%20table%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_all_data%20%3D%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%23%20DR%20training%20compounds%20(excludes%20test%20set%20%E2%80%94%20in_test%20flag%20is%20always%20False%20for%20DR)%0A%20%20%20%20_dr_data%20%3D%20_all_data.filter(pl.col(_TARGET_COL).is_not_null())%0A%20%20%20%20%23%20Extra%20SD-only%20compounds%20for%20%2Bsd_rows%20scenario%0A%20%20%20%20_sd_only%20%20%3D%20_all_data.filter(%0A%20%20%20%20%20%20%20%20pl.col(%22in_single_dose%22)%20%26%20~pl.col(%22in_dose_response%22)%0A%20%20%20%20)%0A%0A%20%20%20%20def%20_make_Y(df%3A%20pl.DataFrame%2C%20target_cols%3A%20list%5Bstr%5D)%3A%0A%20%20%20%20%20%20%20%20%22%22%22Build%20a%20sparse%20(n%20%C3%97%20k)%20target%20matrix%2C%20omitting%20NaN%20entries.%22%22%22%0A%20%20%20%20%20%20%20%20_rows%2C%20_cols%2C%20_vals%20%3D%20%5B%5D%2C%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_j%2C%20_tc%20in%20enumerate(target_cols)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_tc%20not%20in%20df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20_arr%20%3D%20df%5B_tc%5D.to_numpy().astype(float)%0A%20%20%20%20%20%20%20%20%20%20%20%20_mask%20%3D%20~np.isnan(_arr)%0A%20%20%20%20%20%20%20%20%20%20%20%20_rows.extend(np.where(_mask)%5B0%5D.tolist())%0A%20%20%20%20%20%20%20%20%20%20%20%20_cols.extend(%5B_j%5D%20*%20int(_mask.sum()))%0A%20%20%20%20%20%20%20%20%20%20%20%20_vals.extend(_arr%5B_mask%5D.tolist())%0A%20%20%20%20%20%20%20%20return%20_sp.coo_matrix(%0A%20%20%20%20%20%20%20%20%20%20%20%20(np.array(_vals)%2C%20(np.array(_rows)%2C%20np.array(_cols)))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20shape%3D(len(df)%2C%20len(target_cols))%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Check%20%2F%20resume%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_is_complete%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED_PATH.exists()%0A%20%20%20%20%20%20%20%20and%20_EXPECTED%20%3C%3D%20set(pl.read_csv(_PRED_PATH)%5B%22scenario%22%5D.unique().to_list())%0A%20%20%20%20)%0A%0A%20%20%20%20if%20_is_complete%3A%0A%20%20%20%20%20%20%20%20print(f%22All%20scenarios%20complete%20%E2%80%94%20loading%20%7B_PRED_PATH%7D.%22)%0A%20%20%20%20%20%20%20%20macau_mt_pred_df%20%3D%20pl.read_csv(_PRED_PATH)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20if%20_PRED_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%3A%20list%5Bdict%5D%20%3D%20pl.read_csv(_PRED_PATH).to_dicts()%0A%20%20%20%20%20%20%20%20elif%20_CKPT_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20pl.read_csv(_CKPT_PATH).to_dicts()%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all_records%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20_done_scenarios%3A%20dict%5Bstr%2C%20set%5Bint%5D%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sc%3A%20%7Br%5B%22fold%22%5D%20for%20r%20in%20_all_records%20if%20r%5B%22scenario%22%5D%20%3D%3D%20sc%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20sc%20in%20_SCENARIOS%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20def%20_ckpt(records%3A%20list%5Bdict%5D)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20records%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%0A%20%20%20%20%20%20%20%20%20%20%20%20_CKPT_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20with%20gzip.open(_CKPT_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(records).write_csv(_f)%0A%0A%20%20%20%20%20%20%20%20for%20_sc%20in%20_SCENARIOS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_folds%20%3D%20_done_scenarios%5B_sc%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(_done_folds)%20%3E%3D%20_N_FOLDS%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_sc%7D%3A%20already%20complete%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_done_folds%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_sc%7D%3A%20resuming%20from%20fold%20%7Blen(_done_folds)%7D%2F%7B_N_FOLDS%7D%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20Which%20target%20columns%20does%20this%20scenario%20use%3F%0A%20%20%20%20%20%20%20%20%20%20%20%20_target_cols%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%2Bsd_cols%22%3A%20%20%20%20%5B_TARGET_COL%2C%20_SD_COL%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%2Bcounter%22%3A%20%20%20%20%5B_TARGET_COL%2C%20_COUNTER_COL%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%2Bsd_counter%22%3A%20%5B_TARGET_COL%2C%20_SD_COL%2C%20_COUNTER_COL%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%2Bsd_rows%22%3A%20%20%20%20%5B_TARGET_COL%2C%20_SD_COL%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%5B_sc%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_remaining%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20t%20for%20t%20in%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_dr_data%2C%20n_outer%3D_N_OUTER%2C%20n_inner%3D_N_INNER%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20t%5B0%5D%20not%20in%20_done_folds%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_train_dr%2C%20_%2C%20_test_dr%20in%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_remaining%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D_N_FOLDS%20-%20len(_done_folds)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc%3Df%22Macau%20multitask%20%2F%20%7B_sc%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20For%20%2Bsd_rows%3A%20append%20SD-only%20compounds%20to%20the%20training%20split%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_sc%20%3D%3D%20%22%2Bsd_rows%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_rows%20%3D%20pl.concat(%5B_train_dr%2C%20_sd_only%5D%2C%20how%3D%22diagonal%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_rows%20%3D%20_train_dr%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20CheMeleon%20embeddings%20(subprocess%20to%20avoid%20OpenMP%20conflict)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Xtr%2C%20_Xte%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_rows%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_dr%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20prefix%3Df%22a4_%7B_sc%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20Build%20sparse%20Y%20matrix%20for%20training%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_Y_train%20%3D%20_make_Y(_train_rows%2C%20_target_cols)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%3D%20MacauModel()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model.train(_Xtr%2C%20_Y_train)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20predict%20returns%20(n_test%20%C3%97%20k)%3B%20column%200%20%3D%20pEC50_dr%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds_all%20%3D%20_model.predict(_Xte)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds%20%3D%20_preds_all%5B%3A%2C%200%5D%20if%20_preds_all.ndim%20%3D%3D%202%20else%20_preds_all%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_model%2C%20_Xtr%2C%20_Xte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true%20%3D%20_test_dr%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_dr%5B%22inchikey%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_dr%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_dr%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_y_true.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all_records.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22scenario%22%3A%20_sc%2C%20%22method%22%3A%20f%22macau_%7B_sc%7D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ckpt(_all_records)%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_sc%7D%3A%20done.%22)%0A%0A%20%20%20%20%20%20%20%20_PRED_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20with%20gzip.open(_PRED_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_all_records).write_csv(_f)%0A%20%20%20%20%20%20%20%20_CKPT_PATH.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20print(f%22All%20done%20%E2%80%94%20%7Blen(_all_records)%3A%2C%7D%20rows%20%E2%86%92%20%7B_PRED_PATH%7D%22)%0A%0A%20%20%20%20%20%20%20%20macau_mt_pred_df%20%3D%20pl.read_csv(_PRED_PATH)%0A%20%20%20%20return%20(macau_mt_pred_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20Path%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20macau_mt_pred_df%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20pl%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Compare%20the%20multitask%20Macau%20scenarios%20against%20the%20single-task%20base%0A%20%20%20%20(macau_chemeleon%20from%20Analysis%201)%20and%20the%20CheMeleon%20fine-tuned%20baseline.%0A%20%20%20%20%22%22%22%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20base%3A%20macau%20chemeleon%20single-task%20from%20Analysis%201%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_macau_base%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F4_fp_model_comparison_1.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22method%22)%20%3D%3D%20%22macau_chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22base%22).alias(%22scenario%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22macau_base%22).alias(%22method%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22random%22).alias(%22split%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20)%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20CheMeleon%20fine-tuned%20baseline%20from%20notebook%202%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_chemeleon_base%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F2_ml_baseline_5x5cv_random_predictions.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22model%22)%20%3D%3D%20%22chemeleon%22)%0A%20%20%20%20%20%20%20%20.rename(%7B%22model%22%3A%20%22scenario%22%2C%20%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22chemeleon_base%22).alias(%22scenario%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22chemeleon_base%22).alias(%22method%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.lit(%22random%22).alias(%22split%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20)%0A%20%20%20%20_macau_ref%20%3D%20(%0A%20%20%20%20%20%20%20%20macau_mt_pred_df%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%0A%20%20%20%20_combined4%20%3D%20pl.concat(%5B_macau_base%2C%20_macau_ref%2C%20_chemeleon_base%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics4%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_combined4%2C%0A%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%0A%20%20%20%20%20%20%20%20pred_col%3D%22y_pred%22%2C%0A%20%20%20%20%20%20%20%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_summary4%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics4%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_SHORTEN4%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22macau_base%22%3A%20%20%20%20%20%20%20%20%22base%22%2C%0A%20%20%20%20%20%20%20%20%22chemeleon_base%22%3A%20%20%20%20%22che_base%22%2C%0A%20%20%20%20%20%20%20%20%22macau_%2Bsd_cols%22%3A%20%20%20%20%22%2Bsd%22%2C%0A%20%20%20%20%20%20%20%20%22macau_%2Bcounter%22%3A%20%20%20%20%22%2Bctr%22%2C%0A%20%20%20%20%20%20%20%20%22macau_%2Bsd_counter%22%3A%20%22%2Bsd%2Bctr%22%2C%0A%20%20%20%20%20%20%20%20%22macau_%2Bsd_rows%22%3A%20%20%20%20%22%2Bsd_rows%22%2C%0A%20%20%20%20%7D%0A%20%20%20%20_metrics4_plot%20%3D%20_metrics4.with_columns(%0A%20%20%20%20%20%20%20%20pl.col(%22method%22).replace(_SHORTEN4)%0A%20%20%20%20)%0A%0A%20%20%20%20_fig4%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics4_plot%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(10%2C%2010)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.1%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR%20%2F%20%22analysis4_macau_multitask_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%204%20%E2%80%94%20Multitask%20Macau%20(CheMeleon)%20vs%20single-task%20baseline%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary4.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20MCS%20grid%20%E2%80%94%20MAE%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig4)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Analysis%205%20%E2%80%94%20HPO%20for%20RF%2C%20XGBoost%2C%20Macau%20and%20TabPFN%0A%0A%20%20%20%20Optuna%20TPE%20hyperparameter%20optimisation%20(50%20trials%2C%201%C3%975%20CV%2C%20objective%20%3D%20mean%20MAE).%0A%0A%20%20%20%20%7C%20Model%20%7C%20Fingerprint%20%7C%20Parameters%20tuned%20%7C%0A%20%20%20%20%7C-------%7C-------------%7C-----------------%7C%0A%20%20%20%20%7C%20RF%20%7C%20Mordred%20%7C%20n%5C_estimators%2C%20max%5C_depth%2C%20min%5C_samples%5C_leaf%2C%20max%5C_features%20%7C%0A%20%20%20%20%7C%20XGBoost%20%7C%20Mordred%20%7C%20n%5C_estimators%2C%20max%5C_depth%2C%20learning%5C_rate%2C%20subsample%2C%20colsample%5C_bytree%2C%20reg%5C_alpha%20%7C%0A%20%20%20%20%7C%20Macau%20%7C%20CheMeleon%20%7C%20num%5C_latent%2C%20nsamples%2C%20burnin%20%7C%0A%0A%20%20%20%20Each%20study%20is%20persisted%20to%20SQLite%20(%60predictions%2F4_hpo_*.db%60)%20and%20can%20be%20resumed.%0A%20%20%20%20Best%20params%20are%20then%20evaluated%20with%205%C3%975%20CV%20and%20compared%20via%20MCS.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20BoostedTreesModel%2C%0A%20%20%20%20MacauModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20RandomForestModel%2C%0A%20%20%20%20calc_regression_metrics%2C%0A%20%20%20%20chemeleon_embed%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20gc%2C%0A%20%20%20%20generate_cv_splits_random%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20gzip%2C%0A%20%20%20%20make_mcs_plot_grid%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20np%2C%0A%20%20%20%20optuna%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20tqdm%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Analysis%205%3A%20Optuna%20HPO%20(50%20trials%2C%201%C3%975%20CV)%20for%20RF%2FXGB%20on%20Mordred%20and%0A%20%20%20%20Macau%20on%20CheMeleon%2C%20followed%20by%205%C3%975%20CV%20evaluation%20of%20best%20params.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_P_VAL%20%20%20%20%20%20%3D%200.1%0A%20%20%20%20_N_TRIALS%20%20%20%3D%2050%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Load%20training%20data%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_dr_train5%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(_TARGET_COL).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22molecule_names%22%2C%20_TARGET_COL%5D)%0A%20%20%20%20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Determine%20upfront%20whether%20all%20work%20is%20already%20done%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_PRED5_PATH%20%20%20%3D%20Path(%22..%2Fpredictions%2F4_hpo_a5_best_5x5cv.csv.gz%22)%0A%20%20%20%20_EXPECTED5%20%20%20%20%3D%20%7B%22rf_mordred_hpo%22%2C%20%22xgb_mordred_hpo%22%2C%20%22macau_che_hpo%22%7D%0A%20%20%20%20_DB_DIR%20%20%20%20%20%20%20%3D%20Path(%22..%2Fpredictions%22)%0A%0A%20%20%20%20_hpo_done%20%3D%20all(%0A%20%20%20%20%20%20%20%20len(%5Bt%20for%20t%20in%20optuna.load_study(%0A%20%20%20%20%20%20%20%20%20%20%20%20study_name%3Dname%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20storage%3Df%22sqlite%3A%2F%2F%2F%7B_DB_DIR%7D%2F%7Bdb%7D%22%2C%0A%20%20%20%20%20%20%20%20).trials%20if%20t.state%20%3D%3D%20optuna.trial.TrialState.COMPLETE%5D)%20%3E%3D%20_N_TRIALS%0A%20%20%20%20%20%20%20%20for%20name%2C%20db%20in%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22rf_mordred_hpo%22%2C%20%20%20%20%20%20%224_hpo_rf_mordred.db%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22xgb_mordred_hpo%22%2C%20%20%20%20%20%224_hpo_xgb_mordred.db%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(%22macau_chemeleon_hpo%22%2C%20%224_hpo_macau_chemeleon.db%22)%2C%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20if%20(_DB_DIR%20%2F%20db).exists()%0A%20%20%20%20)%20if%20all((_DB_DIR%20%2F%20db).exists()%20for%20_%2C%20db%20in%20%5B%0A%20%20%20%20%20%20%20%20(%22rf_mordred_hpo%22%2C%20%20%20%20%20%20%224_hpo_rf_mordred.db%22)%2C%0A%20%20%20%20%20%20%20%20(%22xgb_mordred_hpo%22%2C%20%20%20%20%20%224_hpo_xgb_mordred.db%22)%2C%0A%20%20%20%20%20%20%20%20(%22macau_chemeleon_hpo%22%2C%20%224_hpo_macau_chemeleon.db%22)%2C%0A%20%20%20%20%5D)%20else%20False%0A%0A%20%20%20%20_cv_done%20%3D%20(%0A%20%20%20%20%20%20%20%20_PRED5_PATH.exists()%0A%20%20%20%20%20%20%20%20and%20_EXPECTED5%20%3C%3D%20set(pl.read_csv(_PRED5_PATH)%5B%22method%22%5D.unique().to_list())%0A%20%20%20%20)%0A%0A%20%20%20%20_all_done%20%3D%20_hpo_done%20and%20_cv_done%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Pre-compute%20fingerprint%20matrices%20(skipped%20if%20results%20already%20exist)%20%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20if%20not%20_all_done%3A%0A%20%20%20%20%20%20%20%20%23%20Mordred%20(for%20RF%20%2F%20XGB)%0A%20%20%20%20%20%20%20%20_mordred_fp_all%20%3D%20generate_fingerprint(_dr_train5%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_X_mordred_all%20%20%3D%20extract_fp_matrix(_mordred_fp_all%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20del%20_mordred_fp_all%0A%20%20%20%20%20%20%20%20if%20np.isnan(_X_mordred_all).any()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_valid_cols%20%3D%20~np.isnan(_X_mordred_all).any(axis%3D0)%0A%20%20%20%20%20%20%20%20%20%20%20%20_X_mordred_all%20%3D%20_X_mordred_all%5B%3A%2C%20_valid_cols%5D%0A%0A%20%20%20%20%20%20%20%20%23%20CheMeleon%20(for%20Macau)%20%E2%80%94%20embed%20all%20compounds%20once%20via%20subprocess.%0A%20%20%20%20%20%20%20%20_X_che_all%2C%20_%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20_dr_train5%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_dr_train5%5B%22smiles%22%5D.to_list()%5B%3A1%5D%2C%20%20%23%20dummy%20single-row%20test%3B%20discarded%0A%20%20%20%20%20%20%20%20%20%20%20%20prefix%3D%22a5_precompute%22%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Shared%20inchikey%20%E2%86%92%20row-index%20lookup%20for%20both%20matrices%0A%20%20%20%20%20%20%20%20_ik_to_idx%20%3D%20%7Bik%3A%20i%20for%20i%2C%20ik%20in%20enumerate(_dr_train5%5B%22inchikey%22%5D.to_list())%7D%0A%0A%20%20%20%20%20%20%20%20def%20_idx(df)%20-%3E%20np.ndarray%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20np.array(%5B_ik_to_idx%5Bk%5D%20for%20k%20in%20df%5B%22inchikey%22%5D.to_list()%5D)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20print(%22All%20HPO%20studies%20and%205%C3%975%20CV%20complete%20%E2%80%94%20skipping%20fingerprint%20computation.%22)%0A%20%20%20%20%20%20%20%20_X_mordred_all%20%3D%20_X_che_all%20%3D%20None%0A%0A%20%20%20%20%20%20%20%20def%20_idx(df)%20-%3E%20np.ndarray%3A%20%20%23%20placeholder%3B%20never%20called%20when%20_all_done%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(%22Fingerprints%20not%20computed.%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Helper%3A%201%C3%975%20CV%20MAE%20for%20a%20given%20predict%20function%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20def%20_cv1x5_mae(predict_fn)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20maes%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_tr%2C%20_val%2C%20_te%20in%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20_dr_train5%2C%20n_outer%3D1%2C%20n_inner%3D5%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20maes.append(float(np.abs(predict_fn(_tr%2C%20_val%2C%20_te)%20-%20_te%5B_TARGET_COL%5D.to_numpy()).mean()))%0A%20%20%20%20%20%20%20%20return%20float(np.mean(maes))%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20HPO%20studies%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_DB_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20RF%20on%20Mordred%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20def%20_rf_objective(trial%3A%20optuna.Trial)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20params%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%20%20%20%20%3D%20trial.suggest_int(%22n_estimators%22%2C%20100%2C%20800%2C%20step%3D100)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%20%20%20%20%20%20%20%3D%20trial.suggest_int(%22max_depth%22%2C%203%2C%2030)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20min_samples_leaf%3D%20trial.suggest_int(%22min_samples_leaf%22%2C%201%2C%2020)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_features%20%20%20%20%3D%20trial.suggest_float(%22max_features%22%2C%200.1%2C%201.0)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20def%20_pred(tr%2C%20val%2C%20te)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20m%20%3D%20RandomForestModel(pred_type%3D%22regression%22%2C%20random_state%3D_SEED%2C%20**params)%0A%20%20%20%20%20%20%20%20%20%20%20%20m.train(_X_mordred_all%5B_idx(tr)%5D%2C%20tr%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m.predict(_X_mordred_all%5B_idx(te)%5D)%0A%20%20%20%20%20%20%20%20return%20_cv1x5_mae(_pred)%0A%0A%20%20%20%20_rf_study%20%3D%20optuna.create_study(%0A%20%20%20%20%20%20%20%20study_name%3D%22rf_mordred_hpo%22%2C%0A%20%20%20%20%20%20%20%20storage%3Df%22sqlite%3A%2F%2F%2F%7B_DB_DIR%7D%2F4_hpo_rf_mordred.db%22%2C%0A%20%20%20%20%20%20%20%20load_if_exists%3DTrue%2C%20direction%3D%22minimize%22%2C%0A%20%20%20%20%20%20%20%20sampler%3Doptuna.samplers.TPESampler(seed%3D_SEED)%2C%0A%20%20%20%20)%0A%20%20%20%20_rf_done%20%3D%20len(%5Bt%20for%20t%20in%20_rf_study.trials%20if%20t.state%20%3D%3D%20optuna.trial.TrialState.COMPLETE%5D)%0A%20%20%20%20if%20_rf_done%20%3C%20_N_TRIALS%3A%0A%20%20%20%20%20%20%20%20_rf_study.optimize(_rf_objective%2C%20n_trials%3D_N_TRIALS%20-%20_rf_done%2C%20show_progress_bar%3DTrue)%0A%20%20%20%20_rf_best%20%3D%20%7B**_rf_study.best_params%7D%0A%20%20%20%20print(f%22RF%20best%20(MAE%3D%7B_rf_study.best_value%3A.4f%7D)%3A%20%7B_rf_best%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20XGBoost%20on%20Mordred%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20def%20_xgb_objective(trial%3A%20optuna.Trial)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20params%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20n_estimators%20%20%20%20%20%3D%20trial.suggest_int(%22n_estimators%22%2C%20100%2C%201500%2C%20step%3D100)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20max_depth%20%20%20%20%20%20%20%20%3D%20trial.suggest_int(%22max_depth%22%2C%203%2C%2010)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20learning_rate%20%20%20%20%3D%20trial.suggest_float(%22learning_rate%22%2C%200.01%2C%200.3%2C%20log%3DTrue)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20subsample%20%20%20%20%20%20%20%20%3D%20trial.suggest_float(%22subsample%22%2C%200.5%2C%201.0)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20colsample_bytree%20%3D%20trial.suggest_float(%22colsample_bytree%22%2C%200.5%2C%201.0)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_alpha%20%20%20%20%20%20%20%20%3D%20trial.suggest_float(%22reg_alpha%22%2C%201e-4%2C%2010.0%2C%20log%3DTrue)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20def%20_pred(tr%2C%20val%2C%20te)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20m%20%3D%20BoostedTreesModel(pred_type%3D%22regression%22%2C%20**params)%0A%20%20%20%20%20%20%20%20%20%20%20%20m.train(_X_mordred_all%5B_idx(tr)%5D%2C%20tr%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_mordred_all%5B_idx(val)%5D%2C%20val%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m.predict(_X_mordred_all%5B_idx(te)%5D)%0A%20%20%20%20%20%20%20%20return%20_cv1x5_mae(_pred)%0A%0A%20%20%20%20_xgb_study%20%3D%20optuna.create_study(%0A%20%20%20%20%20%20%20%20study_name%3D%22xgb_mordred_hpo%22%2C%0A%20%20%20%20%20%20%20%20storage%3Df%22sqlite%3A%2F%2F%2F%7B_DB_DIR%7D%2F4_hpo_xgb_mordred.db%22%2C%0A%20%20%20%20%20%20%20%20load_if_exists%3DTrue%2C%20direction%3D%22minimize%22%2C%0A%20%20%20%20%20%20%20%20sampler%3Doptuna.samplers.TPESampler(seed%3D_SEED)%2C%0A%20%20%20%20)%0A%20%20%20%20_xgb_done%20%3D%20len(%5Bt%20for%20t%20in%20_xgb_study.trials%20if%20t.state%20%3D%3D%20optuna.trial.TrialState.COMPLETE%5D)%0A%20%20%20%20if%20_xgb_done%20%3C%20_N_TRIALS%3A%0A%20%20%20%20%20%20%20%20_xgb_study.optimize(_xgb_objective%2C%20n_trials%3D_N_TRIALS%20-%20_xgb_done%2C%20show_progress_bar%3DTrue)%0A%20%20%20%20_xgb_best%20%3D%20%7B**_xgb_study.best_params%7D%0A%20%20%20%20print(f%22XGB%20best%20(MAE%3D%7B_xgb_study.best_value%3A.4f%7D)%3A%20%7B_xgb_best%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Macau%20on%20CheMeleon%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20def%20_macau_objective(trial%3A%20optuna.Trial)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20params%20%3D%20dict(%0A%20%20%20%20%20%20%20%20%20%20%20%20num_latent%20%3D%20trial.suggest_int(%22num_latent%22%2C%208%2C%2064%2C%20step%3D8)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20nsamples%20%20%20%3D%20trial.suggest_int(%22nsamples%22%2C%20100%2C%201000%2C%20step%3D100)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20burnin%20%20%20%20%20%3D%20trial.suggest_int(%22burnin%22%2C%2050%2C%20400%2C%20step%3D50)%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20def%20_pred(tr%2C%20val%2C%20te)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20m%20%3D%20MacauModel(seed%3D_SEED%2C%20**params)%0A%20%20%20%20%20%20%20%20%20%20%20%20m.train(_X_che_all%5B_idx(tr)%5D%2C%20tr%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20m.predict(_X_che_all%5B_idx(te)%5D)%0A%20%20%20%20%20%20%20%20return%20_cv1x5_mae(_pred)%0A%0A%20%20%20%20_mac_study%20%3D%20optuna.create_study(%0A%20%20%20%20%20%20%20%20study_name%3D%22macau_chemeleon_hpo%22%2C%0A%20%20%20%20%20%20%20%20storage%3Df%22sqlite%3A%2F%2F%2F%7B_DB_DIR%7D%2F4_hpo_macau_chemeleon.db%22%2C%0A%20%20%20%20%20%20%20%20load_if_exists%3DTrue%2C%20direction%3D%22minimize%22%2C%0A%20%20%20%20%20%20%20%20sampler%3Doptuna.samplers.TPESampler(seed%3D_SEED)%2C%0A%20%20%20%20)%0A%20%20%20%20_mac_done%20%3D%20len(%5Bt%20for%20t%20in%20_mac_study.trials%20if%20t.state%20%3D%3D%20optuna.trial.TrialState.COMPLETE%5D)%0A%20%20%20%20if%20_mac_done%20%3C%20_N_TRIALS%3A%0A%20%20%20%20%20%20%20%20_mac_study.optimize(_macau_objective%2C%20n_trials%3D_N_TRIALS%20-%20_mac_done%2C%20show_progress_bar%3DTrue)%0A%20%20%20%20_mac_best%20%3D%20%7B**_mac_study.best_params%7D%0A%20%20%20%20print(f%22Macau%20best%20(MAE%3D%7B_mac_study.best_value%3A.4f%7D)%3A%20%7B_mac_best%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%205%C3%975%20CV%20evaluation%20with%20best%20params%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_MODELS5%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22rf_mordred_hpo%22%3A%20%20%20%20(%22rf%22%2C%20%20%20%20_rf_best)%2C%0A%20%20%20%20%20%20%20%20%22xgb_mordred_hpo%22%3A%20%20%20(%22xgb%22%2C%20%20%20_xgb_best)%2C%0A%20%20%20%20%20%20%20%20%22macau_che_hpo%22%3A%20%20%20%20%20(%22macau%22%2C%20_mac_best)%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20_is_complete5%20%3D%20_cv_done%0A%0A%20%20%20%20if%20_is_complete5%3A%0A%20%20%20%20%20%20%20%20print(f%225%C3%975%20CV%20results%20found%20%E2%80%94%20loading.%22)%0A%20%20%20%20%20%20%20%20_pred5_df%20%3D%20pl.read_csv(_PRED5_PATH)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20if%20_PRED5_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all5%3A%20list%5Bdict%5D%20%3D%20pl.read_csv(_PRED5_PATH).to_dicts()%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_all5%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_done5_by_method%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20mk%3A%20%7Br%5B%22fold%22%5D%20for%20r%20in%20_all5%20if%20r%5B%22method%22%5D%20%3D%3D%20mk%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20mk%20in%20_MODELS5%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20for%20_mk%2C%20(_mtype%2C%20_params)%20in%20_MODELS5.items()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_done_folds5%20%3D%20_done5_by_method%5B_mk%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20len(_done_folds5)%20%3E%3D%2025%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_mk%7D%3A%20complete%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_done_folds5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_mk%7D%3A%20resuming%20from%20fold%20%7Blen(_done_folds5)%7D%2F25%22)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_fold%2C%20_outer%2C%20_inner%2C%20_tr%2C%20_val%2C%20_te%20in%20tqdm(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bt%20for%20t%20in%20generate_cv_splits_random(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_dr_train5%2C%20n_outer%3D5%2C%20n_inner%3D5%2C%20seed%3D_SEED%2C%20p_val%3D_P_VAL%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%20if%20t%5B0%5D%20not%20in%20_done_folds5%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20total%3D25%20-%20len(_done_folds5)%2C%20desc%3Df%225%C3%975%20CV%20%7B_mk%7D%22%2C%20unit%3D%22fold%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_mtype%20%3D%3D%20%22rf%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20RandomForestModel(pred_type%3D%22regression%22%2C%20random_state%3D_SEED%2C%20**_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_mordred_all%5B_idx(_tr)%5D%2C%20_tr%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds5%20%3D%20_m.predict(_X_mordred_all%5B_idx(_te)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20_mtype%20%3D%3D%20%22xgb%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20BoostedTreesModel(pred_type%3D%22regression%22%2C%20**_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_mordred_all%5B_idx(_tr)%5D%2C%20_tr%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_X_mordred_all%5B_idx(_val)%5D%2C%20_val%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds5%20%3D%20_m.predict(_X_mordred_all%5B_idx(_te)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20elif%20_mtype%20%3D%3D%20%22macau%22%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20MacauModel(seed%3D_SEED%2C%20**_params)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_m.train(_X_che_all%5B_idx(_tr)%5D%2C%20_tr%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_preds5%20%3D%20_m.predict(_X_che_all%5B_idx(_te)%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20del%20_m%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20gc.collect()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_ik%2C%20_mn%2C%20_smi%2C%20_yt%2C%20_yp%20in%20zip(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_te%5B%22inchikey%22%5D.to_list()%2C%20_te%5B%22molecule_names%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_te%5B%22smiles%22%5D.to_list()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_te%5B_TARGET_COL%5D.to_numpy().tolist()%2C%20_preds5.tolist()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_all5.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22inchikey%22%3A%20_ik%2C%20%22molecule_names%22%3A%20_mn%2C%20%22smiles%22%3A%20_smi%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22fold%22%3A%20_fold%2C%20%22outer_fold%22%3A%20_outer%2C%20%22inner_fold%22%3A%20_inner%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22method%22%3A%20_mk%2C%20%22y_true%22%3A%20_yt%2C%20%22y_pred%22%3A%20_yp%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20checkpoint%20after%20each%20fold%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_PRED5_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20with%20gzip.open(_PRED5_PATH%2C%20%22wb%22)%20as%20_f%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(_all5).write_csv(_f)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_mk%7D%3A%20done.%22)%0A%0A%20%20%20%20%20%20%20%20_pred5_df%20%3D%20pl.read_csv(_PRED5_PATH)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Analysis%3A%20metrics%20%2B%20MCS%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Add%20Analysis%201%20baselines%20for%20context%3A%20rf_mordred%20and%20macau_chemeleon%0A%20%20%20%20_a1_baseline%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fpredictions%2F4_fp_model_comparison_1.csv.gz%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(%22method%22).is_in(%5B%22rf_mordred%22%2C%20%22macau_chemeleon%22%5D))%0A%20%20%20%20%20%20%20%20.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D)%0A%20%20%20%20%20%20%20%20.with_columns(pl.lit(%22random%22).alias(%22split%22))%0A%20%20%20%20)%0A%20%20%20%20_pred5_combined%20%3D%20pl.concat(%5B%0A%20%20%20%20%20%20%20%20_pred5_df.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D).with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20_a1_baseline%2C%0A%20%20%20%20%5D%2C%20how%3D%22diagonal%22)%0A%0A%20%20%20%20_metrics5%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20_pred5_combined%2C%20cycle_col%3D%22cv_cycle%22%2C%0A%20%20%20%20%20%20%20%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20)%0A%20%20%20%20_summary5%20%3D%20(%0A%20%20%20%20%20%20%20%20_metrics5%0A%20%20%20%20%20%20%20%20.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22mse%22%2C%20%22r2%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20.sort(%22mae%22)%0A%20%20%20%20)%0A%0A%20%20%20%20_PLOTS_DIR5%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR5.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig5%20%3D%20make_mcs_plot_grid(%0A%20%20%20%20%20%20%20%20_metrics5%2C%0A%20%20%20%20%20%20%20%20stats%3D%5B%22mae%22%5D%2C%0A%20%20%20%20%20%20%20%20group_col%3D%22method%22%2C%0A%20%20%20%20%20%20%20%20figsize%3D(12%2C%2012)%2C%0A%20%20%20%20%20%20%20%20effect_dict%3D%7B%22mae%22%3A%200.1%7D%2C%0A%20%20%20%20%20%20%20%20sort_axes%3DTrue%2C%0A%20%20%20%20%20%20%20%20save_path%3D_PLOTS_DIR5%20%2F%20%22analysis5_hpo_mcs_mae.png%22%2C%0A%20%20%20%20)%0A%0A%20%20%20%20_best_table%20%3D%20pl.DataFrame(%5B%0A%20%20%20%20%20%20%20%20%7B%22model%22%3A%20%22RF%20(Mordred)%22%2C%20%20%20%20%20%20%20**%7Bk%3A%20str(v)%20for%20k%2C%20v%20in%20_rf_best.items()%7D%2C%20%20%22cv_mae%22%3A%20round(_rf_study.best_value%2C%20%204)%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22model%22%3A%20%22XGBoost%20(Mordred)%22%2C%20%20**%7Bk%3A%20str(v)%20for%20k%2C%20v%20in%20_xgb_best.items()%7D%2C%20%22cv_mae%22%3A%20round(_xgb_study.best_value%2C%204)%7D%2C%0A%20%20%20%20%20%20%20%20%7B%22model%22%3A%20%22Macau%20(CheMeleon)%22%2C%20%20**%7Bk%3A%20str(v)%20for%20k%2C%20v%20in%20_mac_best.items()%7D%2C%20%22cv_mae%22%3A%20round(_mac_study.best_value%2C%204)%7D%2C%0A%20%20%20%20%5D)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%205%20%E2%80%94%20HPO%20best%20params%20(1%C3%975%20CV)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_best_table.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%205%C3%975%20CV%20evaluation%20vs%20untuned%20baselines%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary5.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20MCS%20grid%20%E2%80%94%20MAE%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig5)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Analysis%206%20%E2%80%94%20Ensemble%20sweep%20over%20HPO%20model%20predictions%0A%0A%20%20%20%20Exhaustive%20weighted-average%20ensemble%20over%20six%20HPO-tuned%20models%3A%0A%0A%20%20%20%20%7C%20Short%20%7C%20Model%20%7C%20FP%20%7C%0A%20%20%20%20%7C-------%7C-------%7C----%7C%0A%20%20%20%20%7C%20%60cp%60%20%7C%20Chemprop%20HPO%20%7C%20graph%20%7C%0A%20%20%20%20%7C%20%60ch%60%20%7C%20CheMeleon%20HPO%20%7C%20graph%20%7C%0A%20%20%20%20%7C%20%60rf%60%20%7C%20RF%20HPO%20%7C%20Mordred%20%7C%0A%20%20%20%20%7C%20%60xg%60%20%7C%20XGBoost%20HPO%20%7C%20Mordred%20%7C%0A%20%20%20%20%7C%20%60mc%60%20%7C%20Macau%20HPO%20%7C%20CheMeleon%20%7C%0A%20%20%20%20%7C%20%60tf%60%20%7C%20TabPFN%20%7C%20CheMeleon%20%7C%0A%0A%20%20%20%20Each%20weight%20%E2%88%88%20%7B0%2C%20%E2%85%95%2C%20%C2%BC%2C%20%E2%85%93%2C%20%C2%BD%2C%201%2C%202%2C%203%2C%204%2C%205%7D.%20Ratio-duplicate%20combinations%20are%20pruned%0A%20%20%20%20(e.g.%20all-2%20%3D%20all-1)%2C%20and%20at%20least%202%20models%20must%20be%20active%20(weight%20%3E%200).%0A%20%20%20%20Combinations%20are%20evaluated%20one%20at%20a%20time%20to%20avoid%20memory%20pressure%20%E2%80%94%20only%0A%20%20%20%20per-fold%20MAE%20and%20Spearman%20%CF%81%20are%20kept%20per%20combo%2C%20not%20the%20full%20prediction%20matrix.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20calc_regression_metrics%2C%20gzip%2C%20mcs_plot%2C%20mo%2C%20np%2C%20pl%2C%20plt%2C%20tqdm)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Sweep%20all%20ratio-distinct%20weight%20combinations%20for%206%20HPO%20models.%0A%0A%20%20%20%20Processes%20one%20combination%20at%20a%20time%3A%20computes%20weighted-average%20predictions%0A%20%20%20%20across%20all%2025%20folds%2C%20calculates%20MAE%20and%20Spearman%20%CF%81%2C%20appends%20a%20single-row%0A%20%20%20%20summary%20to%20the%20output%20CSV%2C%20then%20moves%20to%20the%20next%20combination.%20%20Memory%20use%0A%20%20%20%20is%20constant%20regardless%20of%20search%20space%20size.%20%20Resumes%20automatically%20if%20the%0A%20%20%20%20output%20file%20already%20exists.%0A%20%20%20%20%22%22%22%0A%20%20%20%20import%20itertools%0A%20%20%20%20from%20fractions%20import%20Fraction%0A%0A%20%20%20%20_OUT_PATH%20%3D%20Path(%22..%2Fpredictions%2F4_ensemble_sweep_metrics.csv.gz%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Model%20definitions%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_MODELS%20%3D%20%5B%0A%20%20%20%20%20%20%20%20(%22cp%22%2C%20%20Path(%22..%2Fpredictions%2F4_hpo_best_5x5cv.csv.gz%22)%2C%20%20%20%20%20%20%20%20%22method%22%2C%20%22chemprop_hpo%22)%2C%0A%20%20%20%20%20%20%20%20(%22ch%22%2C%20%20Path(%22..%2Fpredictions%2F4_hpo_best_5x5cv.csv.gz%22)%2C%20%20%20%20%20%20%20%20%22method%22%2C%20%22chemeleon_hpo%22)%2C%0A%20%20%20%20%20%20%20%20(%22rf%22%2C%20%20Path(%22..%2Fpredictions%2F4_hpo_a5_best_5x5cv.csv.gz%22)%2C%20%20%20%20%20%22method%22%2C%20%22rf_mordred_hpo%22)%2C%0A%20%20%20%20%20%20%20%20(%22xg%22%2C%20%20Path(%22..%2Fpredictions%2F4_hpo_a5_best_5x5cv.csv.gz%22)%2C%20%20%20%20%20%22method%22%2C%20%22xgb_mordred_hpo%22)%2C%0A%20%20%20%20%20%20%20%20(%22mc%22%2C%20%20Path(%22..%2Fpredictions%2F4_hpo_a5_best_5x5cv.csv.gz%22)%2C%20%20%20%20%20%22method%22%2C%20%22macau_che_hpo%22)%2C%0A%20%20%20%20%20%20%20%20(%22tf%22%2C%20%20Path(%22..%2Fpredictions%2F4_fp_model_comparison_2.csv.gz%22)%2C%20%22method%22%2C%20%22tabpfn_chemeleon%22)%2C%0A%20%20%20%20%5D%0A%20%20%20%20_TAGS%20%3D%20%5Bm%5B0%5D%20for%20m%20in%20_MODELS%5D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Build%20ratio-distinct%20weight%20matrix%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_W_frac%20%3D%20%5BFraction(0)%2C%20Fraction(1%2C%205)%2C%20Fraction(1%2C%204)%2C%20Fraction(1%2C%203)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Fraction(1%2C%202)%2C%20Fraction(1)%2C%20Fraction(2)%2C%20Fraction(3)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Fraction(4)%2C%20Fraction(5)%5D%0A%20%20%20%20_W_vals%20%3D%20np.array(%5B0.0%2C%201%2F5%2C%201%2F4%2C%201%2F3%2C%201%2F2%2C%201.0%2C%202.0%2C%203.0%2C%204.0%2C%205.0%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dtype%3Dnp.float64)%0A%0A%20%20%20%20_seen%3A%20set%20%3D%20set()%0A%20%20%20%20_combos_idx%3A%20list%5Btuple%5Bint%2C%20...%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20for%20_idx_tuple%20in%20itertools.product(range(10)%2C%20repeat%3D6)%3A%0A%20%20%20%20%20%20%20%20_w_frac%20%3D%20tuple(_W_frac%5Bi%5D%20for%20i%20in%20_idx_tuple)%0A%20%20%20%20%20%20%20%20if%20sum(x%20%3E%200%20for%20x%20in%20_w_frac)%20%3C%202%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20_min_nz%20%3D%20min(x%20for%20x%20in%20_w_frac%20if%20x%20%3E%200)%0A%20%20%20%20%20%20%20%20_norm%20%3D%20tuple(x%20%2F%20_min_nz%20for%20x%20in%20_w_frac)%0A%20%20%20%20%20%20%20%20if%20_norm%20not%20in%20_seen%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_seen.add(_norm)%0A%20%20%20%20%20%20%20%20%20%20%20%20_combos_idx.append(_idx_tuple)%0A%0A%20%20%20%20_W_mat%20%3D%20_W_vals%5Bnp.array(_combos_idx)%5D%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20(n_combos%2C%206)%0A%20%20%20%20_W_norm%20%3D%20(_W_mat%20%2F%20_W_mat.sum(axis%3D1%2C%20keepdims%3DTrue))%20%20%20%20%20%23%20normalised%20rows%0A%20%20%20%20_n_combos%20%3D%20len(_combos_idx)%0A%20%20%20%20_combo_to_row%20%3D%20%7Bt%3A%20i%20for%20i%2C%20t%20in%20enumerate(_combos_idx)%7D%20%20%23%20O(1)%20lookup%0A%0A%20%20%20%20def%20_combo_label(idx_tuple%3A%20tuple%5Bint%2C%20...%5D)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%22%22%22Compact%20label%20e.g.%20'cp1_ch2_rf0_xg1_mc0_tf3'.%22%22%22%0A%20%20%20%20%20%20%20%20_label_map%20%3D%20%7B0%3A%20%220%22%2C%201%3A%20%2215%22%2C%202%3A%20%2214%22%2C%203%3A%20%2213%22%2C%204%3A%20%2212%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%205%3A%20%221%22%2C%206%3A%20%222%22%2C%207%3A%20%223%22%2C%208%3A%20%224%22%2C%209%3A%20%225%22%7D%0A%20%20%20%20%20%20%20%20return%20%22_%22.join(f%22%7Btag%7D%7B_label_map%5Bi%5D%7D%22%20for%20tag%2C%20i%20in%20zip(_TAGS%2C%20idx_tuple))%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Load%20and%20pivot%20predictions%20(done%20once%20regardless%20of%20cache%20state)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_parts%20%3D%20%5B%5D%0A%20%20%20%20for%20_tag%2C%20_path%2C%20_col%2C%20_val%20in%20_MODELS%3A%0A%20%20%20%20%20%20%20%20_parts.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.read_csv(_path)%0A%20%20%20%20%20%20%20%20%20%20%20%20.filter(pl.col(_col)%20%3D%3D%20_val)%0A%20%20%20%20%20%20%20%20%20%20%20%20.select(%5B%22inchikey%22%2C%20%22fold%22%2C%20%22y_true%22%2C%20pl.col(%22y_pred%22).alias(_tag)%5D)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20_wide%20%3D%20_parts%5B0%5D%0A%20%20%20%20for%20_p%20in%20_parts%5B1%3A%5D%3A%0A%20%20%20%20%20%20%20%20_wide%20%3D%20_wide.join(_p.drop(%22y_true%22)%2C%20on%3D%5B%22inchikey%22%2C%20%22fold%22%5D)%0A%0A%20%20%20%20%23%20Pre-extract%20per-fold%20numpy%20arrays%20%E2%80%94%20avoids%20repeated%20Polars%20filtering%0A%20%20%20%20_folds%20%3D%20sorted(_wide%5B%22fold%22%5D.unique().to_list())%0A%20%20%20%20_fold_arrays%3A%20list%5Btuple%5Bnp.ndarray%2C%20np.ndarray%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20for%20_f%20in%20_folds%3A%0A%20%20%20%20%20%20%20%20_fd%20%3D%20_wide.filter(pl.col(%22fold%22)%20%3D%3D%20_f)%0A%20%20%20%20%20%20%20%20_fold_arrays.append((%0A%20%20%20%20%20%20%20%20%20%20%20%20_fd.select(_TAGS).to_numpy().astype(np.float64)%2C%20%20%23%20(n_cmp%2C%206)%0A%20%20%20%20%20%20%20%20%20%20%20%20_fd%5B%22y_true%22%5D.to_numpy().astype(np.float64)%2C%20%20%20%20%20%20%20%23%20(n_cmp%2C)%0A%20%20%20%20%20%20%20%20))%0A%0A%20%20%20%20def%20_spearman(a%3A%20np.ndarray%2C%20b%3A%20np.ndarray)%20-%3E%20float%3A%0A%20%20%20%20%20%20%20%20%22%22%22Spearman%20%CF%81%20between%201-D%20arrays%20a%20and%20b.%22%22%22%0A%20%20%20%20%20%20%20%20_ra%20%3D%20np.argsort(np.argsort(a)).astype(np.float64)%0A%20%20%20%20%20%20%20%20_rb%20%3D%20np.argsort(np.argsort(b)).astype(np.float64)%0A%20%20%20%20%20%20%20%20_da%2C%20_db%20%3D%20_ra%20-%20_ra.mean()%2C%20_rb%20-%20_rb.mean()%0A%20%20%20%20%20%20%20%20_denom%20%3D%20_da.std()%20*%20_db.std()%0A%20%20%20%20%20%20%20%20return%20float(_da%20%40%20_db%20%2F%20(len(_ra)%20*%20_denom))%20if%20_denom%20%3E%200%20else%200.0%0A%0A%20%20%20%20if%20_OUT_PATH.exists()%3A%0A%20%20%20%20%20%20%20%20_done_labels%20%3D%20set(pl.read_csv(_OUT_PATH)%5B%22ensemble%22%5D.to_list())%0A%20%20%20%20%20%20%20%20print(f%22Resuming%20%E2%80%94%20%7Blen(_done_labels)%3A%2C%7D%20of%20%7B_n_combos%3A%2C%7D%20combos%20already%20done.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_done_labels%20%3D%20set()%0A%20%20%20%20%20%20%20%20_OUT_PATH.parent.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20One%20combination%20at%20a%20time%2C%20appending%20metrics%20to%20file%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_header_written%20%3D%20_OUT_PATH.exists()%0A%20%20%20%20_remaining%20%3D%20%5Bt%20for%20t%20in%20_combos_idx%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_combo_label(t)%20not%20in%20_done_labels%5D%0A%0A%20%20%20%20with%20gzip.open(_OUT_PATH%2C%20%22ab%22%20if%20_header_written%20else%20%22wb%22)%20as%20_fh%3A%0A%20%20%20%20%20%20%20%20for%20_idx_t%20in%20tqdm(_remaining%2C%20total%3Dlen(_remaining)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20desc%3D%22Ensemble%20sweep%22%2C%20unit%3D%22combo%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_w%20%3D%20_W_norm%5B_combo_to_row%5B_idx_t%5D%5D%20%20%20%20%20%20%20%23%20normalised%20weight%20vector%20(6%2C)%0A%20%20%20%20%20%20%20%20%20%20%20%20_fold_maes%2C%20_fold_rhos%20%3D%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_P%2C%20_y%20in%20_fold_arrays%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ens%20%3D%20_P%20%40%20_w%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20(n_cmp%2C)%20%E2%80%94%20single%20combo%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fold_maes.append(float(np.abs(_ens%20-%20_y).mean()))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_fold_rhos.append(_spearman(_ens%2C%20_y))%0A%20%20%20%20%20%20%20%20%20%20%20%20_row%20%3D%20pl.DataFrame(%5B%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ensemble%22%3A%20_combo_label(_idx_t)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20**%7Bf%22w_%7Btag%7D%22%3A%20_W_vals%5B_idx_t%5Bi%5D%5D%20for%20i%2C%20tag%20in%20enumerate(_TAGS)%7D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22mae%22%3A%20float(np.mean(_fold_maes))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22rho%22%3A%20float(np.mean(_fold_rhos))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20_row.write_csv(_fh%2C%20include_header%3Dnot%20_header_written)%0A%20%20%20%20%20%20%20%20%20%20%20%20_header_written%20%3D%20True%0A%0A%20%20%20%20_sweep_df%20%3D%20pl.read_csv(_OUT_PATH)%0A%20%20%20%20print(f%22Done%20%E2%80%94%20%7Blen(_sweep_df)%3A%2C%7D%20combos%20in%20%7B_OUT_PATH%7D%22)%0A%0A%20%20%20%20from%20statsmodels.stats.multicomp%20import%20pairwise_tukeyhsd%0A%20%20%20%20import%20pandas%20as%20_pd%0A%0A%20%20%20%20_top20%20%3D%20_sweep_df.sort(%22mae%22).head(20)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Individual%20model%20summary%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_indiv_records%20%3D%20%5B%5D%0A%20%20%20%20for%20_tag%2C%20_path%2C%20_col%2C%20_val%20in%20_MODELS%3A%0A%20%20%20%20%20%20%20%20_df%20%3D%20pl.read_csv(_path).filter(pl.col(_col)%20%3D%3D%20_val)%0A%20%20%20%20%20%20%20%20_metrics%20%3D%20calc_regression_metrics(%0A%20%20%20%20%20%20%20%20%20%20%20%20_df.rename(%7B%22fold%22%3A%20%22cv_cycle%22%7D).with_columns(pl.lit(%22random%22).alias(%22split%22))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cycle_col%3D%22cv_cycle%22%2C%20val_col%3D%22y_true%22%2C%20pred_col%3D%22y_pred%22%2C%20thresh%3D4.0%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_indiv_records.append(%0A%20%20%20%20%20%20%20%20%20%20%20%20_metrics.group_by(%22method%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg(pl.col(%5B%22mae%22%2C%20%22rho%22%5D).mean())%0A%20%20%20%20%20%20%20%20%20%20%20%20.with_columns(pl.lit(_tag).alias(%22model%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.select(%5B%22model%22%2C%20%22mae%22%2C%20%22rho%22%5D)%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20_indiv_summary%20%3D%20pl.concat(_indiv_records).sort(%22mae%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Per-model%20weight%20MCS%20heatmaps%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20For%20each%20model%20(cp%2C%20ch%2C%20rf%2C%20xg%2C%20mc%2C%20tf)%20group%20all%20ensemble%20combos%20by%20the%0A%20%20%20%20%23%20weight%20assigned%20to%20that%20model%20and%20run%20Tukey%20HSD%20on%20MAE%20across%20weight%20groups.%0A%20%20%20%20%23%20Each%20weight%20level%20becomes%20a%20%22method%22%20in%20the%20heatmap.%0A%0A%20%20%20%20_W_LABEL%20%3D%20%7B0.0%3A%20%220%22%2C%201%2F5%3A%20%221%2F5%22%2C%201%2F4%3A%20%221%2F4%22%2C%201%2F3%3A%20%221%2F3%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201%2F2%3A%20%221%2F2%22%2C%201.0%3A%20%221%22%2C%202.0%3A%20%222%22%2C%203.0%3A%20%223%22%2C%204.0%3A%20%224%22%2C%205.0%3A%20%225%22%7D%0A%0A%20%20%20%20def%20_w_label(v%3A%20float)%20-%3E%20str%3A%0A%20%20%20%20%20%20%20%20%23%20round%20to%20avoid%20float%20comparison%20issues%0A%20%20%20%20%20%20%20%20return%20_W_LABEL.get(round(v%2C%206)%2C%20str(round(v%2C%204)))%0A%0A%20%20%20%20_TAG_FULLNAME%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%22cp%22%3A%20%22Chemprop%20HPO%22%2C%0A%20%20%20%20%20%20%20%20%22ch%22%3A%20%22CheMeleon%20HPO%22%2C%0A%20%20%20%20%20%20%20%20%22rf%22%3A%20%22RF%20HPO%20(Mordred)%22%2C%0A%20%20%20%20%20%20%20%20%22xg%22%3A%20%22XGBoost%20HPO%20(Mordred)%22%2C%0A%20%20%20%20%20%20%20%20%22mc%22%3A%20%22Macau%20HPO%20(CheMeleon)%22%2C%0A%20%20%20%20%20%20%20%20%22tf%22%3A%20%22TabPFN%20(CheMeleon)%22%2C%0A%20%20%20%20%7D%0A%0A%20%20%20%20def%20_weight_mcs_ax(ax%2C%20tag%3A%20str)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20%22%22%22Draw%20a%20Tukey%20HSD%20MCS%20heatmap%20for%20one%20model's%20weight%20levels.%22%22%22%0A%20%20%20%20%20%20%20%20_wcol%20%3D%20f%22w_%7Btag%7D%22%0A%20%20%20%20%20%20%20%20_sweep_pd%20%3D%20_sweep_df.select(%5B_wcol%2C%20%22mae%22%5D).to_pandas()%0A%20%20%20%20%20%20%20%20_sweep_pd%5B%22w_label%22%5D%20%3D%20_sweep_pd%5B_wcol%5D.map(_w_label)%0A%0A%20%20%20%20%20%20%20%20%23%20Sort%20groups%20by%20mean%20MAE%20ascending%0A%20%20%20%20%20%20%20%20_order%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20_sweep_pd.groupby(%22w_label%22)%5B%22mae%22%5D.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort_values().index.tolist()%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_groups%20%3D%20_sweep_pd%5B%22w_label%22%5D.values%0A%20%20%20%20%20%20%20%20_vals%20%20%20%3D%20_sweep_pd%5B%22mae%22%5D.values%0A%0A%20%20%20%20%20%20%20%20_tukey%20%3D%20pairwise_tukeyhsd(_vals%2C%20_groups%2C%20alpha%3D0.05)%0A%20%20%20%20%20%20%20%20_res%20%3D%20_pd.DataFrame(%0A%20%20%20%20%20%20%20%20%20%20%20%20data%20%20%20%20%3D%20_tukey._results_table.data%5B1%3A%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20columns%20%3D%20_tukey._results_table.data%5B0%5D%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Build%20symmetric%20mean-difference%20and%20p-value%20matrices%0A%20%20%20%20%20%20%20%20_means%20%3D%20_sweep_pd.groupby(%22w_label%22)%5B%22mae%22%5D.mean().reindex(_order)%0A%20%20%20%20%20%20%20%20_n%20%20%20%20%20%3D%20len(_order)%0A%20%20%20%20%20%20%20%20_diff%20%20%3D%20_pd.DataFrame(0.0%2C%20index%3D_order%2C%20columns%3D_order)%0A%20%20%20%20%20%20%20%20_pval%20%20%3D%20_pd.DataFrame(1.0%2C%20index%3D_order%2C%20columns%3D_order)%0A%0A%20%20%20%20%20%20%20%20for%20_%2C%20row%20in%20_res.iterrows()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20g1%2C%20g2%20%3D%20str(row%5B%22group1%22%5D)%2C%20str(row%5B%22group2%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20g1%20in%20_order%20and%20g2%20in%20_order%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20d%20%3D%20float(row%5B%22meandiff%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%20%3D%20float(row%5B%22p-adj%22%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_diff.loc%5Bg1%2C%20g2%5D%20%3D%20d%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_diff.loc%5Bg2%2C%20g1%5D%20%3D%20-d%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pval.loc%5Bg1%2C%20g2%5D%20%3D%20p%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_pval.loc%5Bg2%2C%20g1%5D%20%3D%20p%0A%0A%20%20%20%20%20%20%20%20mcs_plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20pc%3D_pval%2C%20effect_size%3D_diff%2C%20means%3D_means%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20ax%3Dax%2C%20show_diff%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20cell_text_size%3D8%2C%20axis_text_size%3D9%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reverse_cmap%3DTrue%2C%20%20%23%20lower%20MAE%20%3D%20better%20%3D%20blue%0A%20%20%20%20%20%20%20%20%20%20%20%20vlim%3D0.05%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ax.set_title(f%22%7B_TAG_FULLNAME%5Btag%5D%7D%20%20(weight%20effect%20on%20MAE)%22%2C%20fontsize%3D11)%0A%0A%20%20%20%20_PLOTS_DIR6%20%3D%20Path(%22..%2Fplots%2F4_ml_optimization_2%22)%0A%20%20%20%20_PLOTS_DIR6.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_fig_w%2C%20_axes_w%20%3D%20plt.subplots(3%2C%202%2C%20figsize%3D(14%2C%2022))%0A%20%20%20%20for%20_ax%2C%20_tag%20in%20zip(_axes_w.flatten()%2C%20_TAGS)%3A%0A%20%20%20%20%20%20%20%20_weight_mcs_ax(_ax%2C%20_tag)%0A%20%20%20%20_fig_w.suptitle(%22Weight%20sensitivity%20MCS%20heatmaps%20%E2%80%94%20MAE%20(lower%20%3D%20better%2C%20blue)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20fontsize%3D14)%0A%20%20%20%20_fig_w.tight_layout()%0A%20%20%20%20_fig_w.savefig(_PLOTS_DIR6%20%2F%20%22analysis6_ensemble_mcs_mae.png%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20dpi%3D120%2C%20bbox_inches%3D%22tight%22)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20Analysis%206%20%E2%80%94%20Ensemble%20sweep%20results%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(f%22**%7B_n_combos%3A%2C%7D%20ratio-distinct%20weight%20combinations%20evaluated**%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Individual%20model%20summary%20(sorted%20by%20MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_indiv_summary.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Top-20%20ensembles%20by%20MAE%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_top20.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Weight%20sensitivity%20%E2%80%94%20MCS%20heatmaps%20per%20model%20(MAE)%22)%2C%0A%20%20%20%20%20%20%20%20mo.md(%22Each%20heatmap%20shows%20whether%20changing%20the%20weight%20assigned%20to%20one%20model%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22significantly%20changes%20ensemble%20MAE%20(averaged%20over%20all%20other%20weight%20combinations).%22)%2C%0A%20%20%20%20%20%20%20%20mo.as_html(_fig_w)%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20MPS%20performance%20degradation%20analysis%0A%0A%20%20%20%20Analyses%20inter-fold%20timing%20from%20the%20chemprop%20CLI%20and%20API%20log%20files%20to%0A%20%20%20%20characterise%20how%20macOS%20MPS%20memory%20pressure%20causes%20stalls%20and%20whether%20the%0A%20%20%20%20pattern%20varies%20across%20the%20day%2Fnight%20cycle%20or%20degrades%20over%20multi-day%20runs.%0A%0A%20%20%20%20**Definitions**%0A%20%20%20%20-%20*Normal%20fold*%3A%20inter-fold%20gap%20%E2%89%A4%20150%20s%20(pure%20training%20time%2C%20no%20MPS%20stall)%0A%20%20%20%20-%20*Stall*%3A%20inter-fold%20gap%20%3E%20150%20s%20(MPS%20memory%20reclaim%20blocking%20next%20fold)%0A%20%20%20%20-%20*Stall%20rate*%3A%20fraction%20of%20transitions%20that%20are%20stalls%20in%20a%20given%20window%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20pl)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Parse%20the%20chemprop%20CLI%20log%20into%20a%20DataFrame%20of%20inter-fold%20gaps.%0A%0A%20%20%20%20Each%20training%20block%20starts%20with%20'Running%20in%20mode%20train'%20and%20ends%20at%20the%0A%20%20%20%20'predict%20-%20test%20size'%20line%20within%20that%20block.%20%20Training%20time%20is%20measured%0A%20%20%20%20as%20train-start%20%E2%86%92%20predict-end%2C%20which%20is%20immune%20to%20inter-run%20idle%20gaps%20that%0A%20%20%20%20would%20otherwise%20inflate%20the%20apparent%20time%20of%20the%20first%20fold%20after%20a%20break.%0A%20%20%20%20A%20block%20belongs%20to%20CheMeleon%20if%20it%20contains%20'Loading%20cached%20CheMeleon'.%0A%20%20%20%20%22%22%22%0A%20%20%20%20from%20datetime%20import%20datetime%0A%0A%20%20%20%20_CLI_LOG%20%20%3D%20Path(%22..%2Flogs%2Fchemprop_cli.log%22)%0A%20%20%20%20_STALL_THR%20%3D%20150%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20split%20log%20into%20per-fold%20blocks%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Each%20record%3A%20(train_start%2C%20predict_end%2C%20model).%0A%20%20%20%20%23%20Using%20train_start%20%E2%86%92%20predict_end%20avoids%20contamination%20from%20inter-run%20idle%0A%20%20%20%20%23%20gaps%20that%20would%20inflate%20the%20apparent%20training%20time%20of%20the%20next%20fold.%0A%20%20%20%20_folds%3A%20list%5Btuple%5Bdatetime%2C%20datetime%2C%20str%5D%5D%20%3D%20%5B%5D%0A%20%20%20%20if%20_CLI_LOG.exists()%3A%0A%20%20%20%20%20%20%20%20_lines%20%3D%20_CLI_LOG.read_text().splitlines()%0A%20%20%20%20%20%20%20%20_block_lines%3A%20list%5Bstr%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20_block_start%3A%20datetime%20%7C%20None%20%3D%20None%0A%20%20%20%20%20%20%20%20for%20_line%20in%20_lines%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20%22Running%20in%20mode%20'train'%22%20in%20_line%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20flush%20previous%20block%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_block_lines%20and%20_block_start%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_is_che%20%3D%20any(%22Loading%20cached%20CheMeleon%22%20in%20bl%20for%20bl%20in%20_block_lines)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_model%20%20%3D%20%22chemeleon%22%20if%20_is_che%20else%20%22chemprop%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_bl%20in%20_block_lines%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20%22chemprop.cli.predict%20-%20test%20size%22%20in%20_bl%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ts_end%20%3D%20datetime.fromisoformat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_bl.split(%22%20-%20%22)%5B0%5D.strip().replace(%22T%22%2C%20%22%20%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_folds.append((_block_start%2C%20_ts_end%2C%20_model))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pass%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_block_start%20%3D%20datetime.fromisoformat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_line.split(%22%20-%20%22)%5B0%5D.strip().replace(%22T%22%2C%20%22%20%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_block_start%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_block_lines%20%3D%20%5B_line%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_block_lines.append(_line)%0A%20%20%20%20%20%20%20%20%23%20flush%20last%20block%0A%20%20%20%20%20%20%20%20if%20_block_lines%20and%20_block_start%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_is_che%20%3D%20any(%22Loading%20cached%20CheMeleon%22%20in%20bl%20for%20bl%20in%20_block_lines)%0A%20%20%20%20%20%20%20%20%20%20%20%20_model%20%20%3D%20%22chemeleon%22%20if%20_is_che%20else%20%22chemprop%22%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_bl%20in%20_block_lines%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20%22chemprop.cli.predict%20-%20test%20size%22%20in%20_bl%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_ts_end%20%3D%20datetime.fromisoformat(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_bl.split(%22%20-%20%22)%5B0%5D.strip().replace(%22T%22%2C%20%22%20%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_folds.append((_block_start%2C%20_ts_end%2C%20_model))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20except%20Exception%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20pass%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20break%0A%0A%20%20%20%20_t0%20%3D%20_folds%5B0%5D%5B0%5D%20if%20_folds%20else%20None%0A%20%20%20%20_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_i%2C%20(_t_start%2C%20_t_end%2C%20_model)%20in%20enumerate(_folds)%3A%0A%20%20%20%20%20%20%20%20_train_s%20%3D%20(_t_end%20-%20_t_start).total_seconds()%0A%20%20%20%20%20%20%20%20_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22fold_idx%22%3A%20%20_i%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20%20%20%20%20_model%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22t_start%22%3A%20%20%20_t_start%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22gap_s%22%3A%20%20%20%20%20_train_s%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22hour%22%3A%20%20%20%20%20%20_t_start.hour%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22date%22%3A%20%20%20%20%20%20_t_start.date().isoformat()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22is_stall%22%3A%20%20_train_s%20%3E%20_STALL_THR%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22elapsed_h%22%3A%20(_t_start%20-%20_t0).total_seconds()%20%2F%203600%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%0A%20%20%20%20timing_df%20%3D%20pl.DataFrame(_rows)%0A%20%20%20%20_by_model%20%3D%20(%0A%20%20%20%20%20%20%20%20timing_df.group_by(%22model%22)%0A%20%20%20%20%20%20%20%20.agg(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.len().alias(%22n_gaps%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.col(%22is_stall%22).sum().alias(%22n_stalls%22)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20(pl.col(%22is_stall%22).mean()%20*%20100).round(1).alias(%22stall_pct%22)%2C%0A%20%20%20%20%20%20%20%20%5D)%0A%20%20%20%20%20%20%20%20.sort(%22model%22)%0A%20%20%20%20)%0A%20%20%20%20print(timing_df.group_by(%22model%22).len())%0A%20%20%20%20print(_by_model)%0A%20%20%20%20return%20(timing_df%2C)%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20np%2C%20pl%2C%20plt%2C%20sns%2C%20timing_df)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Three%20plots%20split%20by%20model%20(chemprop%20vs%20chemeleon)%2C%20saved%20to%20plots%2F%3A%0A%20%20%20%20%20%201.%20Stall%20rate%20by%20hour%20of%20day%0A%20%20%20%20%20%202.%20Rolling%20stall%20rate%20over%20elapsed%20run%20time%0A%20%20%20%20%20%203.%20Gap%20duration%20distribution%0A%20%20%20%20%22%22%22%0A%20%20%20%20_STALL_THR%20%3D%20150%0A%20%20%20%20_PLOTS_DIR%20%3D%20Path(%22..%2Fplots%22)%20%2F%20%224_ml_optimization_2%22%0A%20%20%20%20_PLOTS_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%0A%20%20%20%20_MODELS%20%20%20%3D%20%5B%22chemprop%22%2C%20%22chemeleon%22%5D%0A%20%20%20%20_COLORS%20%20%20%3D%20%7B%22chemprop%22%3A%20%22tab%3Ablue%22%2C%20%22chemeleon%22%3A%20%22tab%3Aorange%22%7D%0A%20%20%20%20_LABELS%20%20%20%3D%20%7B%22chemprop%22%3A%20%22Chemprop%20(scratch)%22%2C%20%22chemeleon%22%3A%20%22CheMeleon%22%7D%0A%0A%20%20%20%20sns.set_style(%22whitegrid%22)%0A%20%20%20%20sns.set_context(%22notebook%22%2C%20font_scale%3D1.2)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20per-model%20data%20prep%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20def%20_hourly_df(df%3A%20pl.DataFrame)%20-%3E%20%22pd.DataFrame%22%3A%0A%20%20%20%20%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20df.group_by(%22hour%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.agg((pl.col(%22is_stall%22).mean()%20*%20100).alias(%22stall_pct%22))%0A%20%20%20%20%20%20%20%20%20%20%20%20.sort(%22hour%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20.to_pandas()%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20def%20_rolling(df%3A%20pl.DataFrame%2C%20win%3A%20float%20%3D%202.0)%20-%3E%20%22pd.DataFrame%22%3A%0A%20%20%20%20%20%20%20%20_eh%20%3D%20df%5B%22elapsed_h%22%5D.to_numpy()%0A%20%20%20%20%20%20%20%20_st%20%3D%20df%5B%22is_stall%22%5D.to_numpy().astype(float)%0A%20%20%20%20%20%20%20%20_rows%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_i%2C%20_e%20in%20enumerate(_eh)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_m%20%3D%20(_eh%20%3E%3D%20_e%20-%20win)%20%26%20(_eh%20%3C%3D%20_e)%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20_m.sum()%20%3E%3D%203%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_rows.append(%7B%22elapsed_h%22%3A%20_e%2C%20%22roll_pct%22%3A%20float(_st%5B_m%5D.mean()%20*%20100)%7D)%0A%20%20%20%20%20%20%20%20return%20pl.DataFrame(_rows).sort(%22elapsed_h%22).to_pandas()%20if%20_rows%20else%20None%0A%0A%20%20%20%20_subsets%20%3D%20%7Bm%3A%20timing_df.filter(pl.col(%22model%22)%20%3D%3D%20m)%20for%20m%20in%20_MODELS%7D%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20plot%201%3A%20stall%20rate%20by%20hour%20(chemprop%20only)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_fig1%2C%20_ax1%20%3D%20plt.subplots(figsize%3D(10%2C%205))%0A%20%20%20%20_h%20%3D%20_hourly_df(_subsets%5B%22chemprop%22%5D)%0A%20%20%20%20_ax1.plot(_h%5B%22hour%22%5D%2C%20_h%5B%22stall_pct%22%5D%2C%20marker%3D%22o%22%2C%20color%3D_COLORS%5B%22chemprop%22%5D)%0A%20%20%20%20_ax1.fill_between(_h%5B%22hour%22%5D%2C%20_h%5B%22stall_pct%22%5D%2C%20alpha%3D0.2%2C%20color%3D_COLORS%5B%22chemprop%22%5D)%0A%20%20%20%20_ax1.axhspan(0%2C%2030%2C%20alpha%3D0.07%2C%20color%3D%22green%22)%0A%20%20%20%20_ax1.text(0.5%2C%2025%2C%20%22target%20zone%20(%E2%89%A430%25)%22%2C%20color%3D%22green%22%2C%20alpha%3D0.7%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20transform%3D_ax1.get_yaxis_transform()%2C%20fontsize%3D9)%0A%20%20%20%20_ax1.set_xlabel(%22Hour%20of%20day%22)%0A%20%20%20%20_ax1.set_ylabel(%22Stall%20rate%20(%25)%22)%0A%20%20%20%20_ax1.set_title(%22Chemprop%20(scratch)%20%E2%80%94%20stall%20rate%20by%20hour%20of%20day%22)%0A%20%20%20%20_ax1.set_xticks(range(0%2C%2024%2C%202))%0A%20%20%20%20_ax1.set_ylim(0%2C%20105)%0A%20%20%20%20_fig1.tight_layout()%0A%20%20%20%20_fig1.savefig(_PLOTS_DIR%20%2F%20%22mps_stall_by_hour.png%22%2C%20dpi%3D150%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.close(_fig1)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20plot%202%3A%20rolling%20stall%20rate%20over%20elapsed%20run%20time%20(chemprop%20only)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_fig2%2C%20_ax2%20%3D%20plt.subplots(figsize%3D(10%2C%205))%0A%20%20%20%20_r%20%3D%20_rolling(_subsets%5B%22chemprop%22%5D)%0A%20%20%20%20if%20_r%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20_ax2.plot(_r%5B%22elapsed_h%22%5D%2C%20_r%5B%22roll_pct%22%5D%2C%20color%3D_COLORS%5B%22chemprop%22%5D%2C%20alpha%3D0.85)%0A%20%20%20%20_ax2.axhline(50%2C%20color%3D%22red%22%2C%20linestyle%3D%22--%22%2C%20alpha%3D0.5%2C%20label%3D%2250%25%20threshold%22)%0A%20%20%20%20_ax2.set_xlabel(%22Elapsed%20run%20time%20(hours)%22)%0A%20%20%20%20_ax2.set_ylabel(%22Rolling%20stall%20rate%20%25%20(2%20h%20window)%22)%0A%20%20%20%20_ax2.set_title(%22Chemprop%20(scratch)%20%E2%80%94%20performance%20degradation%20over%20run%20time%22)%0A%20%20%20%20_ax2.set_ylim(0%2C%20105)%0A%20%20%20%20_ax2.legend()%0A%20%20%20%20_fig2.tight_layout()%0A%20%20%20%20_fig2.savefig(_PLOTS_DIR%20%2F%20%22mps_stall_over_time.png%22%2C%20dpi%3D150%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.close(_fig2)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20plot%202b%3A%20individual%20fold%20times%20over%20elapsed%20run%20time%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_fig2b%2C%20_ax2b%20%3D%20plt.subplots(figsize%3D(12%2C%205))%0A%20%20%20%20_df_cp%20%3D%20_subsets%5B%22chemprop%22%5D.to_pandas()%0A%20%20%20%20_ax2b.scatter(%0A%20%20%20%20%20%20%20%20_df_cp%5B%22elapsed_h%22%5D%2C%20_df_cp%5B%22gap_s%22%5D%20%2F%2060%2C%0A%20%20%20%20%20%20%20%20color%3D_COLORS%5B%22chemprop%22%5D%2C%20alpha%3D0.5%2C%20s%3D18%2C%0A%20%20%20%20)%0A%20%20%20%20_ax2b.axhline(_STALL_THR%20%2F%2060%2C%20color%3D%22red%22%2C%20%20%20%20linestyle%3D%22--%22%2C%20alpha%3D0.6%2C%20label%3Df%22stall%20threshold%20(%7B_STALL_THR%7D%20s)%22)%0A%20%20%20%20_ax2b.axhline(15%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22gray%22%2C%20%20%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.6%2C%20label%3D%2215%20min%22)%0A%20%20%20%20_ax2b.axhline(30%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22olive%22%2C%20%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.6%2C%20label%3D%2230%20min%22)%0A%20%20%20%20_ax2b.axhline(60%2C%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22purple%22%2C%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.6%2C%20label%3D%221%20h%22)%0A%20%20%20%20_ax2b.set_yscale(%22log%22)%0A%20%20%20%20_ax2b.set_xlabel(%22Elapsed%20run%20time%20(hours)%22)%0A%20%20%20%20_ax2b.set_ylabel(%22Fold%20wall-clock%20time%20(min%2C%20log%20scale)%22)%0A%20%20%20%20_ax2b.set_title(%22Chemprop%20(scratch)%20%E2%80%94%20individual%20fold%20times%20over%20elapsed%20run%20time%22)%0A%20%20%20%20_ax2b.legend(loc%3D%22upper%20left%22%2C%20fontsize%3D9)%0A%20%20%20%20_fig2b.tight_layout()%0A%20%20%20%20_fig2b.savefig(_PLOTS_DIR%20%2F%20%22mps_fold_times_scatter.png%22%2C%20dpi%3D150%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.close(_fig2b)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20plot%203%3A%20gap%20distribution%20(log-log)%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_all_gaps%20%3D%20timing_df%5B%22gap_s%22%5D.to_numpy()%0A%20%20%20%20_bins%20%3D%20np.logspace(np.log10(_all_gaps.min()%20%2B%201)%2C%20np.log10(_all_gaps.max())%2C%2050)%0A%20%20%20%20_fig3%2C%20_ax3%20%3D%20plt.subplots(figsize%3D(10%2C%205))%0A%20%20%20%20for%20_m%20in%20_MODELS%3A%0A%20%20%20%20%20%20%20%20_gaps_m%20%3D%20_subsets%5B_m%5D%5B%22gap_s%22%5D.to_numpy()%0A%20%20%20%20%20%20%20%20_ax3.hist(_gaps_m%2C%20bins%3D_bins%2C%20color%3D_COLORS%5B_m%5D%2C%20alpha%3D0.6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20label%3D_LABELS%5B_m%5D%2C%20log%3DTrue)%0A%20%20%20%20_ax3.set_xscale(%22log%22)%0A%20%20%20%20_ax3.axvline(_STALL_THR%2C%20color%3D%22red%22%2C%20%20%20%20linestyle%3D%22--%22%2C%20alpha%3D0.7%2C%20label%3Df%22stall%20threshold%20(%7B_STALL_THR%7D%20s)%22)%0A%20%20%20%20_ax3.axvline(1_800%2C%20%20%20%20%20%20color%3D%22olive%22%2C%20%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.7%2C%20label%3D%2230%20min%22)%0A%20%20%20%20_ax3.axvline(3_600%2C%20%20%20%20%20%20color%3D%22purple%22%2C%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.7%2C%20label%3D%221%20h%22)%0A%20%20%20%20_ax3.axvline(10_800%2C%20%20%20%20%20color%3D%22brown%22%2C%20%20linestyle%3D%22%3A%22%2C%20%20alpha%3D0.7%2C%20label%3D%223%20h%22)%0A%20%20%20%20_ax3.set_xlabel(%22Inter-fold%20gap%20(s%2C%20log%20scale)%22)%0A%20%20%20%20_ax3.set_ylabel(%22Count%20(log%20scale)%22)%0A%20%20%20%20_ax3.set_title(%22Chemprop%20CLI%20%E2%80%94%20distribution%20of%20inter-fold%20gaps%22)%0A%20%20%20%20_ax3.legend()%0A%20%20%20%20_fig3.tight_layout()%0A%20%20%20%20_fig3.savefig(_PLOTS_DIR%20%2F%20%22mps_gap_distribution.png%22%2C%20dpi%3D150%2C%20bbox_inches%3D%22tight%22)%0A%20%20%20%20plt.close(_fig3)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20summary%20table%20per%20model%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_summary_rows%20%3D%20%5B%5D%0A%20%20%20%20for%20_m%20in%20_MODELS%3A%0A%20%20%20%20%20%20%20%20_df_m%20%20%20%3D%20_subsets%5B_m%5D%0A%20%20%20%20%20%20%20%20_normal%20%3D%20_df_m.filter(~pl.col(%22is_stall%22))%5B%22gap_s%22%5D%0A%20%20%20%20%20%20%20%20_stalls%20%3D%20_df_m.filter(pl.col(%22is_stall%22))%5B%22gap_s%22%5D%0A%20%20%20%20%20%20%20%20_summary_rows.append(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model%22%3A%20%20%20%20%20%20%20%20_m%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22n_folds%22%3A%20%20%20%20%20%20len(_df_m)%20%2B%201%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22n_stalls%22%3A%20%20%20%20%20int(_df_m%5B%22is_stall%22%5D.sum())%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22stall_pct%22%3A%20%20%20%20round(100%20*%20float(_df_m%5B%22is_stall%22%5D.mean())%2C%201)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22normal_avg_s%22%3A%20round(float(_normal.mean())%2C%200)%20if%20len(_normal)%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22stall_avg_s%22%3A%20%20round(float(_stalls.mean())%2C%200)%20if%20len(_stalls)%20else%20None%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22stall_max_s%22%3A%20%20round(float(_stalls.max())%2C%20%200)%20if%20len(_stalls)%20else%20None%2C%0A%20%20%20%20%20%20%20%20%7D)%0A%20%20%20%20_summary%20%3D%20pl.DataFrame(_summary_rows)%0A%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%20CLI%20MPS%20stall%20summary%22)%2C%0A%20%20%20%20%20%20%20%20mo.plain_text(_summary.to_pandas().to_string(index%3DFalse))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Stall%20rate%20by%20hour%20of%20day%22)%2C%0A%20%20%20%20%20%20%20%20mo.image(str(_PLOTS_DIR%20%2F%20%22mps_stall_by_hour.png%22))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Performance%20degradation%20over%20run%20time%22)%2C%0A%20%20%20%20%20%20%20%20mo.image(str(_PLOTS_DIR%20%2F%20%22mps_stall_over_time.png%22))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Individual%20fold%20times%20over%20elapsed%20run%20time%22)%2C%0A%20%20%20%20%20%20%20%20mo.image(str(_PLOTS_DIR%20%2F%20%22mps_fold_times_scatter.png%22))%2C%0A%20%20%20%20%20%20%20%20mo.md(%22%23%23%23%20Inter-fold%20gap%20distribution%22)%2C%0A%20%20%20%20%20%20%20%20mo.image(str(_PLOTS_DIR%20%2F%20%22mps_gap_distribution.png%22))%2C%0A%20%20%20%20%5D)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20Submissions%0A%0A%20%20%20%20Train%20each%20model%20on%20the%20full%20dose-response%20training%20set%20and%20predict%20the%0A%20%20%20%20513%20held-out%20test%20compounds.%0A%0A%20%20%20%20**Individual%20submissions**%0A%0A%20%20%20%20%7C%20File%20%7C%20Model%20%7C%0A%20%20%20%20%7C------%7C-------%7C%0A%20%20%20%20%7C%20%604_tabpfn_chemeleon_submission.csv%60%20%7C%20TabPFN%20on%20CheMeleon%20FP%20(default%20params)%20%7C%0A%20%20%20%20%7C%20%604_chemeleon_hpo_submission.csv%60%20%7C%20CheMeleon%20HPO%20best%20params%20%7C%0A%20%20%20%20%7C%20%604_macau_che_hpo_submission.csv%60%20%7C%20Macau%20HPO%20best%20params%20(CheMeleon%20FP)%20%7C%0A%0A%20%20%20%20**Ensemble%20submissions**%20(weighted%20average%20of%20cp%20%2F%20ch%20%2F%20xg%20%2F%20mc%20%2F%20tf%20test%20predictions)%0A%0A%20%20%20%20%7C%20File%20%7C%20Weights%20(cp%20%C2%B7%20ch%20%C2%B7%20rf%20%C2%B7%20xg%20%C2%B7%20mc%20%C2%B7%20tf)%20%7C%0A%20%20%20%20%7C------%7C---------------------------------------%7C%0A%20%20%20%20%7C%20%604_ens_cp4_ch5_rf0_xg1_mc0_tf5_submission.csv%60%20%7C%204%20%C2%B7%205%20%C2%B7%200%20%C2%B7%201%20%C2%B7%200%20%C2%B7%205%20%7C%0A%20%20%20%20%7C%20%604_ens_cp4_ch5_rf0_xg1_mc1_tf5_submission.csv%60%20%7C%204%20%C2%B7%205%20%C2%B7%200%20%C2%B7%201%20%C2%B7%201%20%C2%B7%205%20%7C%0A%20%20%20%20%7C%20%604_ens_cp5_ch5_rf0_xg13_mc1_tf5_submission.csv%60%20%7C%205%20%C2%B7%205%20%C2%B7%200%20%C2%B7%20%E2%85%93%20%C2%B7%201%20%C2%B7%205%20%7C%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20BoostedTreesModel%2C%0A%20%20%20%20ChempropChemeleonModel%2C%0A%20%20%20%20ChempropModel%2C%0A%20%20%20%20MacauModel%2C%0A%20%20%20%20Path%2C%0A%20%20%20%20best_params%2C%0A%20%20%20%20chemeleon_embed%2C%0A%20%20%20%20extract_fp_matrix%2C%0A%20%20%20%20generate_fingerprint%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20np%2C%0A%20%20%20%20pl%2C%0A%20%20%20%20subprocess%2C%0A%20%20%20%20sys%2C%0A%20%20%20%20tempfile%2C%0A)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Train%20all%20models%20on%20the%20full%20training%20set%20and%20generate%20test-set%20predictions.%0A%20%20%20%20Each%20model%20is%20skipped%20if%20its%20output%20file%20already%20exists.%0A%20%20%20%20%22%22%22%0A%20%20%20%20_TARGET_COL%20%3D%20%22pEC50_dr%22%0A%20%20%20%20_SEED%20%20%20%20%20%20%20%3D%2042%0A%20%20%20%20_SUB_DIR%20%20%20%20%3D%20Path(%22..%2Fsubmissions%22)%0A%20%20%20%20_SUB_DIR.mkdir(parents%3DTrue%2C%20exist_ok%3DTrue)%0A%20%20%20%20_tmp%20%20%20%20%20%20%20%20%3D%20Path(tempfile.gettempdir())%0A%20%20%20%20_TABPFN_SCRIPT%20%3D%20_tmp%20%2F%20%22sub_tabpfn.py%22%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%20Load%20datasets%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_train_full%20%3D%20(%0A%20%20%20%20%20%20%20%20pl.read_csv(%22..%2Fdata%2Fprocessed%2Fall_compounds_activity_data.csv%22)%0A%20%20%20%20%20%20%20%20.filter(pl.col(_TARGET_COL).is_not_null())%0A%20%20%20%20%20%20%20%20.select(%5B%22smiles%22%2C%20%22inchikey%22%2C%20%22molecule_names%22%2C%20_TARGET_COL%5D)%0A%20%20%20%20)%0A%20%20%20%20_test_df%20%3D%20pl.read_csv(%22..%2Fdata%2Fraw%2F20260409%2Fdose_response_test.csv%22)%0A%20%20%20%20_test_smiles%20%3D%20_test_df%5B%22SMILES%22%5D.to_list()%0A%20%20%20%20_test_names%20%20%3D%20_test_df%5B%22Molecule%20Name%22%5D.to_list()%0A%0A%20%20%20%20%23%2010%20%25%20val%20split%20for%20models%20that%20need%20early%20stopping%0A%20%20%20%20_rng%20%20%20%20%20%20%3D%20np.random.default_rng(_SEED)%0A%20%20%20%20_n%20%20%20%20%20%20%20%20%3D%20len(_train_full)%0A%20%20%20%20_val_idx%20%20%3D%20_rng.choice(_n%2C%20size%3Dint(_n%20*%200.1)%2C%20replace%3DFalse)%0A%20%20%20%20_tr_idx%20%20%20%3D%20np.setdiff1d(np.arange(_n)%2C%20_val_idx)%0A%20%20%20%20_train_sub%20%3D%20_train_full%5B_tr_idx.tolist()%5D%0A%20%20%20%20_val_sub%20%20%20%3D%20_train_full%5B_val_idx.tolist()%5D%0A%0A%20%20%20%20def%20_save_submission(path%3A%20Path%2C%20preds%3A%20np.ndarray)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20pl.DataFrame(%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22SMILES%22%3A%20%20%20%20%20%20%20%20_test_smiles%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Molecule%20Name%22%3A%20_test_names%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22pEC50%22%3A%20%20%20%20%20%20%20%20%20preds.tolist()%2C%0A%20%20%20%20%20%20%20%20%7D).write_csv(path)%0A%20%20%20%20%20%20%20%20print(f%22Saved%20%7Blen(preds)%7D%20predictions%20%E2%86%92%20%7Bpath.name%7D%22)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%201.%20TabPFN%20on%20CheMeleon%20FP%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_tfn_path%20%3D%20_SUB_DIR%20%2F%20%224_tabpfn_chemeleon_submission.csv%22%0A%20%20%20%20if%20_tfn_path.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22tabpfn_chemeleon%3A%20already%20exists%20%E2%80%94%20skipping.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_TABPFN_SCRIPT.write_text(%22%5Cn%22.join(%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22import%20os%2C%20sys%2C%20numpy%20as%20np%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22os.environ%5B'KMP_DUPLICATE_LIB_OK'%5D%20%3D%20'TRUE'%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20dotenv%20import%20load_dotenv%3B%20from%20pathlib%20import%20Path%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22load_dotenv(Path('.env'))%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22import%20torch%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22torch.set_num_threads(max(1%2C%20(os.cpu_count()%20or%201)%20-%201))%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22from%20tabpfn%20import%20TabPFNRegressor%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22X_train%20%3D%20np.load(sys.argv%5B1%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22y_train%20%3D%20np.load(sys.argv%5B2%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22X_test%20%20%3D%20np.load(sys.argv%5B3%5D)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22out%20%20%20%20%20%3D%20sys.argv%5B4%5D%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model%20%3D%20TabPFNRegressor(n_estimators%3D8%2C%20ignore_pretraining_limits%3DTrue%2C%20device%3D'cpu')%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22model.fit(X_train%2C%20y_train)%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22np.save(out%2C%20model.predict(X_test))%22%2C%0A%20%20%20%20%20%20%20%20%5D))%0A%0A%20%20%20%20%20%20%20%20_Xtr_che%2C%20_Xte_che%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_full%5B%22smiles%22%5D.to_list()%2C%20_test_smiles%2C%20prefix%3D%22sub_tfn%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ytr%20%3D%20_train_full%5B_TARGET_COL%5D.to_numpy()%0A%0A%20%20%20%20%20%20%20%20_f_Xtr%20%3D%20_tmp%20%2F%20%22sub_Xtr.npy%22%3B%20_f_ytr%20%3D%20_tmp%20%2F%20%22sub_ytr.npy%22%0A%20%20%20%20%20%20%20%20_f_Xte%20%3D%20_tmp%20%2F%20%22sub_Xte.npy%22%3B%20_f_out%20%3D%20_tmp%20%2F%20%22sub_preds%22%0A%20%20%20%20%20%20%20%20np.save(str(_f_Xtr)%2C%20_Xtr_che)%3B%20np.save(str(_f_ytr)%2C%20_ytr)%0A%20%20%20%20%20%20%20%20np.save(str(_f_Xte)%2C%20_Xte_che)%0A%20%20%20%20%20%20%20%20_res%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20str(_TABPFN_SCRIPT)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20str(_f_Xtr)%2C%20str(_f_ytr)%2C%20str(_f_Xte)%2C%20str(_f_out)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%20cwd%3Dstr(Path(%22..%2F%22).resolve())%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_res.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22TabPFN%20failed%3A%5Cn%7B_res.stderr%7D%22)%0A%20%20%20%20%20%20%20%20_tfn_preds%20%3D%20np.load(str(_f_out)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20for%20_p%20in%20%5B_f_Xtr%2C%20_f_ytr%2C%20_f_Xte%2C%20Path(str(_f_out)%20%2B%20%22.npy%22)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_p.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20_save_submission(_tfn_path%2C%20_tfn_preds)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%202.%20CheMeleon%20HPO%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_ch_path%20%3D%20_SUB_DIR%20%2F%20%224_chemeleon_hpo_submission.csv%22%0A%20%20%20%20if%20_ch_path.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22chemeleon_hpo%3A%20already%20exists%20%E2%80%94%20skipping.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_chemeleon_params%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20k%3A%20v%20for%20k%2C%20v%20in%20%7B**best_params%2C%20%22epochs%22%3A%2050%7D.items()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20k%20not%20in%20(%22message_hidden_dim%22%2C%20%22depth%22)%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20_ch_model%20%3D%20ChempropChemeleonModel(pred_type%3D%22regression%22%2C%20**_chemeleon_params)%0A%20%20%20%20%20%20%20%20_ch_model.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_sub%5B%22smiles%22%5D.to_list()%2C%20_train_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_val_sub%5B%22smiles%22%5D.to_list()%2C%20%20%20_val_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_save_submission(_ch_path%2C%20_ch_model.predict(_test_smiles))%0A%20%20%20%20%20%20%20%20del%20_ch_model%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%203.%20Macau%20HPO%20on%20CheMeleon%20FP%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_mac_path%20%3D%20_SUB_DIR%20%2F%20%224_macau_che_hpo_submission.csv%22%0A%20%20%20%20if%20_mac_path.exists()%3A%0A%20%20%20%20%20%20%20%20print(f%22macau_che_hpo%3A%20already%20exists%20%E2%80%94%20skipping.%22)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20import%20optuna%20as%20_optuna%0A%20%20%20%20%20%20%20%20_optuna.logging.set_verbosity(_optuna.logging.WARNING)%0A%20%20%20%20%20%20%20%20_mac_study%20%3D%20_optuna.load_study(%0A%20%20%20%20%20%20%20%20%20%20%20%20study_name%3D%22macau_chemeleon_hpo%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20storage%3D%22sqlite%3A%2F%2F%2F..%2Fpredictions%2F4_hpo_macau_chemeleon.db%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_mac_best%20%3D%20_mac_study.best_params%0A%20%20%20%20%20%20%20%20_Xtr_mac%2C%20_Xte_mac%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_full%5B%22smiles%22%5D.to_list()%2C%20_test_smiles%2C%20prefix%3D%22sub_mac%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_mac_model%20%3D%20MacauModel(seed%3D_SEED%2C%20**_mac_best)%0A%20%20%20%20%20%20%20%20_mac_model.train(_Xtr_mac%2C%20_train_full%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20_save_submission(_mac_path%2C%20_mac_model.predict(_Xte_mac))%0A%20%20%20%20%20%20%20%20del%20_mac_model%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%204.%20Ensemble%20test%20predictions%20%E2%80%94%20train%20cp%2C%20ch%2C%20xg%2C%20mc%2C%20tf%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20%23%20Individual%20test%20predictions%20for%20each%20ensemble%20component%0A%20%20%20%20_ens_preds%3A%20dict%5Bstr%2C%20np.ndarray%5D%20%3D%20%7B%7D%0A%0A%20%20%20%20%23%20cp%20%E2%80%94%20Chemprop%20HPO%0A%20%20%20%20_cp_cache%20%3D%20_tmp%20%2F%20%22sub_ens_cp.npy%22%0A%20%20%20%20if%20_cp_cache.exists()%3A%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22cp%22%5D%20%3D%20np.load(str(_cp_cache))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_cp_model%20%3D%20ChempropModel(pred_type%3D%22regression%22%2C%20**%7B**best_params%2C%20%22epochs%22%3A%2050%7D)%0A%20%20%20%20%20%20%20%20_cp_model.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_sub%5B%22smiles%22%5D.to_list()%2C%20_train_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_val_sub%5B%22smiles%22%5D.to_list()%2C%20%20%20_val_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22cp%22%5D%20%3D%20_cp_model.predict(_test_smiles)%0A%20%20%20%20%20%20%20%20np.save(str(_cp_cache)%2C%20_ens_preds%5B%22cp%22%5D)%0A%20%20%20%20%20%20%20%20del%20_cp_model%0A%0A%20%20%20%20%23%20ch%20%E2%80%94%20CheMeleon%20HPO%20(reuse%20if%20already%20trained%20above)%0A%20%20%20%20_ch_cache%20%3D%20_tmp%20%2F%20%22sub_ens_ch.npy%22%0A%20%20%20%20if%20_ch_cache.exists()%3A%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22ch%22%5D%20%3D%20np.load(str(_ch_cache))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_ch2%20%3D%20ChempropChemeleonModel(pred_type%3D%22regression%22%2C%20**_chemeleon_params)%0A%20%20%20%20%20%20%20%20_ch2.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_sub%5B%22smiles%22%5D.to_list()%2C%20_train_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_val_sub%5B%22smiles%22%5D.to_list()%2C%20%20%20_val_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20target_col%3D_TARGET_COL%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22ch%22%5D%20%3D%20_ch2.predict(_test_smiles)%0A%20%20%20%20%20%20%20%20np.save(str(_ch_cache)%2C%20_ens_preds%5B%22ch%22%5D)%0A%20%20%20%20%20%20%20%20del%20_ch2%0A%0A%20%20%20%20%23%20xg%20%E2%80%94%20XGBoost%20HPO%20on%20Mordred%0A%20%20%20%20_xg_cache%20%3D%20_tmp%20%2F%20%22sub_ens_xg.npy%22%0A%20%20%20%20if%20_xg_cache.exists()%3A%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22xg%22%5D%20%3D%20np.load(str(_xg_cache))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20import%20optuna%20as%20_optuna2%0A%20%20%20%20%20%20%20%20_optuna2.logging.set_verbosity(_optuna2.logging.WARNING)%0A%20%20%20%20%20%20%20%20_xgb_best%20%3D%20_optuna2.load_study(%0A%20%20%20%20%20%20%20%20%20%20%20%20study_name%3D%22xgb_mordred_hpo%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20storage%3D%22sqlite%3A%2F%2F%2F..%2Fpredictions%2F4_hpo_xgb_mordred.db%22%2C%0A%20%20%20%20%20%20%20%20).best_params%0A%20%20%20%20%20%20%20%20_fp_tr%20%3D%20generate_fingerprint(_train_sub%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_fp_va%20%3D%20generate_fingerprint(_val_sub%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_fp_te%20%3D%20generate_fingerprint(%0A%20%20%20%20%20%20%20%20%20%20%20%20pl.DataFrame(%7B%22smiles%22%3A%20_test_smiles%2C%20%22inchikey%22%3A%20_test_names%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22molecule_names%22%3A%20_test_names%7D)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mordred%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_Xtr_xg%20%3D%20extract_fp_matrix(_fp_tr%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_Xva_xg%20%3D%20extract_fp_matrix(_fp_va%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20_Xte_xg%20%3D%20extract_fp_matrix(_fp_te%2C%20%22mordred%22)%0A%20%20%20%20%20%20%20%20del%20_fp_tr%2C%20_fp_va%2C%20_fp_te%0A%20%20%20%20%20%20%20%20if%20np.isnan(_Xtr_xg).any()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_valid%20%3D%20~np.isnan(_Xtr_xg).any(axis%3D0)%0A%20%20%20%20%20%20%20%20%20%20%20%20_Xtr_xg%20%3D%20_Xtr_xg%5B%3A%2C%20_valid%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_Xva_xg%20%3D%20_Xva_xg%5B%3A%2C%20_valid%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20_Xte_xg%20%3D%20_Xte_xg%5B%3A%2C%20_valid%5D%0A%20%20%20%20%20%20%20%20_xg_model%20%3D%20BoostedTreesModel(pred_type%3D%22regression%22%2C%20**_xgb_best)%0A%20%20%20%20%20%20%20%20_xg_model.train(%0A%20%20%20%20%20%20%20%20%20%20%20%20_Xtr_xg%2C%20_train_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20_Xva_xg%2C%20_val_sub%5B_TARGET_COL%5D.to_numpy()%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22xg%22%5D%20%3D%20_xg_model.predict(_Xte_xg)%0A%20%20%20%20%20%20%20%20np.save(str(_xg_cache)%2C%20_ens_preds%5B%22xg%22%5D)%0A%20%20%20%20%20%20%20%20del%20_xg_model%2C%20_Xtr_xg%2C%20_Xva_xg%2C%20_Xte_xg%0A%0A%20%20%20%20%23%20mc%20%E2%80%94%20Macau%20HPO%20on%20CheMeleon%20FP%0A%20%20%20%20_mc_cache%20%3D%20_tmp%20%2F%20%22sub_ens_mc.npy%22%0A%20%20%20%20if%20_mc_cache.exists()%3A%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22mc%22%5D%20%3D%20np.load(str(_mc_cache))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_Xtr_mc%2C%20_Xte_mc%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_full%5B%22smiles%22%5D.to_list()%2C%20_test_smiles%2C%20prefix%3D%22sub_mc%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_mc_ens%20%3D%20MacauModel(seed%3D_SEED%2C%20**_mac_best)%0A%20%20%20%20%20%20%20%20_mc_ens.train(_Xtr_mc%2C%20_train_full%5B_TARGET_COL%5D.to_numpy())%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22mc%22%5D%20%3D%20_mc_ens.predict(_Xte_mc)%0A%20%20%20%20%20%20%20%20del%20_mc_ens%2C%20_Xtr_mc%2C%20_Xte_mc%0A%20%20%20%20%20%20%20%20np.save(str(_mc_cache)%2C%20_ens_preds%5B%22mc%22%5D)%0A%0A%20%20%20%20%23%20tf%20%E2%80%94%20TabPFN%20on%20CheMeleon%20FP%0A%20%20%20%20_tf_cache%20%3D%20_tmp%20%2F%20%22sub_ens_tf.npy%22%0A%20%20%20%20if%20_tf_cache.exists()%3A%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22tf%22%5D%20%3D%20np.load(str(_tf_cache))%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20_Xtr_tf%2C%20_Xte_tf%20%3D%20chemeleon_embed(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_full%5B%22smiles%22%5D.to_list()%2C%20_test_smiles%2C%20prefix%3D%22sub_tf%22%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ytr_tf%20%3D%20_train_full%5B_TARGET_COL%5D.to_numpy()%0A%20%20%20%20%20%20%20%20_f_Xtr2%20%3D%20_tmp%20%2F%20%22sub2_Xtr.npy%22%3B%20_f_ytr2%20%3D%20_tmp%20%2F%20%22sub2_ytr.npy%22%0A%20%20%20%20%20%20%20%20_f_Xte2%20%3D%20_tmp%20%2F%20%22sub2_Xte.npy%22%3B%20_f_out2%20%3D%20_tmp%20%2F%20%22sub2_preds%22%0A%20%20%20%20%20%20%20%20np.save(str(_f_Xtr2)%2C%20_Xtr_tf)%3B%20np.save(str(_f_ytr2)%2C%20_ytr_tf)%0A%20%20%20%20%20%20%20%20np.save(str(_f_Xte2)%2C%20_Xte_tf)%0A%20%20%20%20%20%20%20%20_res_tf%20%3D%20subprocess.run(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5Bsys.executable%2C%20str(_TABPFN_SCRIPT)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20str(_f_Xtr2)%2C%20str(_f_ytr2)%2C%20str(_f_Xte2)%2C%20str(_f_out2)%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20capture_output%3DTrue%2C%20text%3DTrue%2C%20cwd%3Dstr(Path(%22..%2F%22).resolve())%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20if%20_res_tf.returncode%20!%3D%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20RuntimeError(f%22TabPFN%20failed%3A%5Cn%7B_res_tf.stderr%7D%22)%0A%20%20%20%20%20%20%20%20_ens_preds%5B%22tf%22%5D%20%3D%20np.load(str(_f_out2)%20%2B%20%22.npy%22)%0A%20%20%20%20%20%20%20%20for%20_p%20in%20%5B_f_Xtr2%2C%20_f_ytr2%2C%20_f_Xte2%2C%20Path(str(_f_out2)%20%2B%20%22.npy%22)%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_p.unlink(missing_ok%3DTrue)%0A%20%20%20%20%20%20%20%20np.save(str(_tf_cache)%2C%20_ens_preds%5B%22tf%22%5D)%0A%0A%20%20%20%20%23%20%E2%94%80%E2%94%80%205.%20Write%20ensemble%20submissions%20%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%E2%94%80%0A%20%20%20%20_W_MAP%20%3D%20%7B%220%22%3A%200.0%2C%20%2215%22%3A%201%2F5%2C%20%2214%22%3A%201%2F4%2C%20%2213%22%3A%201%2F3%2C%20%2212%22%3A%201%2F2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%221%22%3A%201.0%2C%20%222%22%3A%202.0%2C%20%223%22%3A%203.0%2C%20%224%22%3A%204.0%2C%20%225%22%3A%205.0%7D%0A%20%20%20%20_ENS_TAGS%20%3D%20%5B%22cp%22%2C%20%22ch%22%2C%20%22rf%22%2C%20%22xg%22%2C%20%22mc%22%2C%20%22tf%22%5D%0A%0A%20%20%20%20for%20_ens_label%20in%20%5B%0A%20%20%20%20%20%20%20%20%22cp4_ch5_rf0_xg1_mc0_tf5%22%2C%0A%20%20%20%20%20%20%20%20%22cp4_ch5_rf0_xg1_mc1_tf5%22%2C%0A%20%20%20%20%20%20%20%20%22cp5_ch5_rf0_xg13_mc1_tf5%22%2C%0A%20%20%20%20%5D%3A%0A%20%20%20%20%20%20%20%20_ens_path%20%3D%20_SUB_DIR%20%2F%20f%224_ens_%7B_ens_label%7D_submission.csv%22%0A%20%20%20%20%20%20%20%20if%20_ens_path.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20print(f%22%7B_ens_path.name%7D%3A%20already%20exists%20%E2%80%94%20skipping.%22)%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20_parts_ens%20%3D%20_ens_label.split(%22_%22)%0A%20%20%20%20%20%20%20%20_w_raw%20%3D%20%7Btag%3A%20_W_MAP%5Bp%5Blen(tag)%3A%5D%5D%20for%20tag%2C%20p%20in%20zip(_ENS_TAGS%2C%20_parts_ens)%7D%0A%20%20%20%20%20%20%20%20_total%20%3D%20sum(_w_raw.values())%0A%20%20%20%20%20%20%20%20_ens_pred%20%3D%20sum(%0A%20%20%20%20%20%20%20%20%20%20%20%20_ens_preds%5Btag%5D%20*%20(w%20%2F%20_total)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20tag%2C%20w%20in%20_w_raw.items()%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20w%20%3E%200%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_save_submission(_ens_path%2C%20_ens_pred)%0A%0A%20%20%20%20mo.md(%22%23%23%20Submissions%20generated%20%E2%80%94%20see%20validation%20cell%20below.%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(Path%2C%20mo%2C%20np%2C%20pd)%3A%0A%20%20%20%20%22%22%22%0A%20%20%20%20Validate%20all%20submission%20files%20produced%20in%20this%20notebook.%0A%0A%20%20%20%20Rules%20(matching%20the%20OpenADMET%20activity_validation.py%20spec)%3A%0A%20%20%20%20%20%20-%20Required%20columns%3A%20SMILES%2C%20Molecule%20Name%2C%20pEC50%0A%20%20%20%20%20%20-%20No%20missing%20identifiers%20or%20duplicate%20Molecule%20Names%0A%20%20%20%20%20%20-%20pEC50%20must%20be%20numeric%20and%20finite%0A%20%20%20%20%20%20-%20Exactly%20513%20rows%0A%20%20%20%20%22%22%22%0A%20%20%20%20_ACTIVITY_DATASET_SIZE%20%3D%20513%0A%20%20%20%20_SUB_DIR%20%3D%20Path(%22..%2Fsubmissions%22)%0A%0A%20%20%20%20_SUBMISSION_FILES%20%3D%20%5B%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_tabpfn_chemeleon_submission.csv%22%2C%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_chemeleon_hpo_submission.csv%22%2C%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_macau_che_hpo_submission.csv%22%2C%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_ens_cp4_ch5_rf0_xg1_mc0_tf5_submission.csv%22%2C%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_ens_cp4_ch5_rf0_xg1_mc1_tf5_submission.csv%22%2C%0A%20%20%20%20%20%20%20%20_SUB_DIR%20%2F%20%224_ens_cp5_ch5_rf0_xg13_mc1_tf5_submission.csv%22%2C%0A%20%20%20%20%5D%0A%0A%20%20%20%20def%20_validate(path%3A%20Path)%20-%3E%20tuple%5Bbool%2C%20list%5Bstr%5D%5D%3A%0A%20%20%20%20%20%20%20%20errors%3A%20list%5Bstr%5D%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20if%20not%20path.exists()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20%5Bf%22File%20does%20not%20exist%3A%20%7Bpath%7D%22%5D%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(path)%0A%20%20%20%20%20%20%20%20except%20Exception%20as%20exc%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20%5Bf%22Error%20reading%20CSV%3A%20%7Bexc%7D%22%5D%0A%0A%20%20%20%20%20%20%20%20for%20col%20in%20(%22SMILES%22%2C%20%22Molecule%20Name%22%2C%20%22pEC50%22)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20col%20not%20in%20df.columns%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22Missing%20required%20column%3A%20'%7Bcol%7D'%22)%0A%20%20%20%20%20%20%20%20if%20errors%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20errors%0A%0A%20%20%20%20%20%20%20%20if%20df.empty%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20False%2C%20%5B%22Submission%20is%20empty.%22%5D%0A%0A%20%20%20%20%20%20%20%20if%20df%5B%5B%22SMILES%22%2C%20%22Molecule%20Name%22%5D%5D.isna().any(axis%3D1).sum()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(%22Row(s)%20with%20missing%20identifier%20values.%22)%0A%0A%20%20%20%20%20%20%20%20if%20df%5B%22Molecule%20Name%22%5D.duplicated().sum()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22%7Bdf%5B'Molecule%20Name'%5D.duplicated().sum()%7D%20duplicated%20Molecule%20Name(s).%22)%0A%0A%20%20%20%20%20%20%20%20_numeric%20%3D%20pd.to_numeric(df%5B%22pEC50%22%5D%2C%20errors%3D%22coerce%22)%0A%20%20%20%20%20%20%20%20if%20_numeric.isna().sum()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22pEC50%20has%20%7B_numeric.isna().sum()%7D%20non-numeric%20value(s).%22)%0A%20%20%20%20%20%20%20%20elif%20not%20np.isfinite(_numeric.to_numpy()).all()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22pEC50%20has%20non-finite%20value(s).%22)%0A%0A%20%20%20%20%20%20%20%20if%20len(df)%20!%3D%20_ACTIVITY_DATASET_SIZE%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20errors.append(f%22%7Blen(df)%7D%20rows%2C%20expected%20%7B_ACTIVITY_DATASET_SIZE%7D.%22)%0A%0A%20%20%20%20%20%20%20%20return%20len(errors)%20%3D%3D%200%2C%20errors%0A%0A%20%20%20%20_results%20%3D%20%5B%5D%0A%20%20%20%20for%20_path%20in%20_SUBMISSION_FILES%3A%0A%20%20%20%20%20%20%20%20_ok%2C%20_errs%20%3D%20_validate(_path)%0A%20%20%20%20%20%20%20%20if%20_ok%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_results.append(mo.md(f%22%E2%9C%93%20**%7B_path.name%7D**%20%E2%80%94%20passed%22))%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_results.append(mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20f%22%E2%9C%97%20**%7B_path.name%7D**%20%E2%80%94%20FAILED%3A%5Cn%22%20%2B%20%22%5Cn%22.join(f%22-%20%7Be%7D%22%20for%20e%20in%20_errs)%0A%20%20%20%20%20%20%20%20%20%20%20%20))%0A%0A%20%20%20%20mo.vstack(_results)%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
4fd44cbc40c91ecabadfd44c2ee12112