{ "cells": [ { "cell_type": "markdown", "id": "3a2b8d51", "metadata": {}, "source": [ "# SI Figure S3: inter-method correlation and PCA of NS372 and delta-22 shieldings\n", "\n", "Inter-method correlation matrices and PC1/PC2 loadings for NS372 (44 functionals x 8 nuclei) and\n", "delta22 (18 functionals, ¹H/¹³C), plus a combined summary table." ] }, { "cell_type": "markdown", "id": "0fb6f798", "metadata": {}, "source": [ "Two independent NMR shielding datasets, analyzed separately (never pooled):\n", "\n", "| Dataset | Source | Methods | Nuclei | Reference |\n", "|---|---|---|---|---|\n", "| **NS372** | Schattenberg & Kaupp, *JCTC* **17**, 7602 (2021) | 44 DFT/WFT functionals | ¹H ¹¹B ¹³C ¹⁵N ¹⁷O ¹⁹F ³¹P ³³S | CCSD(T)/pcSseg-3 |\n", "| **delta22** | in-house delta-22 set | 18 gas-phase functionals, largest basis (pcSseg-3; mp2 → pcSseg-2), PBE0/cc-pVTZ geometry | ¹H ¹³C | DSD-PBEP86 (highest-rung in-set method, stands in for CCSD(T)) |" ] }, { "cell_type": "markdown", "id": "3ff927f7", "metadata": {}, "source": [ "## 1. Configuration" ] }, { "cell_type": "code", "execution_count": null, "id": "9e5a25b8", "metadata": {}, "outputs": [], "source": [ "import os, sys\n", "\n", "REPO = os.path.abspath(\"../..\")\n", "for _p in (\"analysis/code\", \"analysis/code/shared\"):\n", " sys.path.insert(0, os.path.join(REPO, _p))" ] }, { "cell_type": "code", "execution_count": null, "id": "06e0333d", "metadata": {}, "outputs": [], "source": [ "import glob\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "import paths\n", "import leveling\n", "import leveling_plots" ] }, { "cell_type": "code", "execution_count": null, "id": "fbd3d68f", "metadata": {}, "outputs": [], "source": [ "# inputs:\n", "# - the Kaupp NS372 spreadsheet is small and ships in the repo's data/ns372/ folder\n", "# - delta22.hdf5 is large: it resolves from the repo's data/delta22/ folder, carried by the\n", "# Hugging Face checkout via Git LFS (see analysis/code/paths.py)\n", "# the delta22 file is encoded as whole numbers (real value x 10,000); the loader\n", "# in leveling.py decodes it on read (a plain-decimal copy also works, since the\n", "# decode step is a no-op on floating-point data).\n", "KAUPP_XLSX = os.path.join(REPO, \"data\", \"ns372\", \"ct1c00919_si_002.xlsx\")\n", "DELTA22_H5 = paths.dataset_file(\"delta22\", root=REPO)\n", "SAVE_DPI = 200 # SI-quality raster output\n", "\n", "def figure_path(name):\n", " os.makedirs(\"figures\", exist_ok=True)\n", " return os.path.join(\"figures\", name)\n", "\n", "# self-clean: this notebook builds PNG names dynamically (one per nucleus), so drop any\n", "# previously written figures before regenerating. Scope the delete to THIS figure's own prefix:\n", "# the figures/ folder is shared with the other notebooks in this directory, and a full reproduce\n", "# run executes them in name order, so a broad \"figures/*.png\" wipe would destroy the panels written\n", "# by notebooks that sort before this one. (glob on a missing folder returns [])\n", "for _stale in glob.glob(os.path.join(\"figures\", \"si_figure_s03_*.png\")):\n", " os.remove(_stale)\n", "\n", "pd.set_option('display.width', 150)\n", "pd.set_option('display.max_columns', 25)" ] }, { "cell_type": "code", "execution_count": null, "id": "84ddcb3f", "metadata": {}, "outputs": [], "source": [ "# functional-family colours used for every figure (family ordering lives in leveling.py)\n", "FAMILY_COLORS = {'LDA':'#777777','GGA':'#1f77b4','mGGA':'#17becf','GH':'#2ca02c',\n", " 'RSH':'#9467bd','LH':'#8c564b','DH':'#ff7f0e','WFT':'#d62728',\n", " 'ref':'#FF1493'}\n", "\n", "# proper Unicode-superscript labels for nuclei in figure titles\n", "NUC_DISPLAY = {'1H':'¹H','11B':'¹¹B','13C':'¹³C',\n", " '15N':'¹⁵N','17O':'¹⁷O','19F':'¹⁹F',\n", " '31P':'³¹P','33S':'³³S'}\n", "\n", "# adjustText parameters tuned per nucleus -- 1H and 13C have very dense central\n", "# clusters and need stronger expansion; the paramagnetic-shielding nuclei\n", "# (15N/17O/19F) already spread methods along the parabola and need a gentler\n", "# pass to avoid over-flinging labels.\n", "PCA_ADJUST_DEFAULT = dict(\n", " force_text=(0.35, 0.55), force_explode=(0.25, 0.40),\n", " force_static=(0.10, 0.15), force_pull=(0.02, 0.02),\n", " expand=(1.25, 1.35), time_lim=4,\n", ")\n", "PCA_ADJUST = {\n", " '1H': {**PCA_ADJUST_DEFAULT, 'force_text':(0.75, 1.05),\n", " 'force_explode':(0.65, 0.90), 'expand':(1.7, 1.9), 'time_lim':7},\n", " '11B': {**PCA_ADJUST_DEFAULT, 'force_text':(0.50, 0.75),\n", " 'force_explode':(0.40, 0.60), 'expand':(1.4, 1.55), 'time_lim':5},\n", " '13C': {**PCA_ADJUST_DEFAULT, 'force_text':(0.70, 1.00),\n", " 'force_explode':(0.60, 0.85), 'expand':(1.6, 1.8), 'time_lim':6},\n", " '15N': {**PCA_ADJUST_DEFAULT, 'force_text':(0.60, 0.85),\n", " 'force_explode':(0.50, 0.70), 'expand':(1.5, 1.65), 'time_lim':6},\n", " '17O': {**PCA_ADJUST_DEFAULT, 'force_text':(0.60, 0.85),\n", " 'force_explode':(0.50, 0.70), 'expand':(1.5, 1.65), 'time_lim':6},\n", " '31P': {**PCA_ADJUST_DEFAULT, 'force_text':(0.65, 0.90),\n", " 'force_explode':(0.55, 0.75), 'expand':(1.55, 1.7), 'time_lim':6},\n", " '33S': {**PCA_ADJUST_DEFAULT, 'force_text':(0.65, 0.90),\n", " 'force_explode':(0.55, 0.75), 'expand':(1.55, 1.7), 'time_lim':6},\n", "}" ] }, { "cell_type": "markdown", "id": "4fbeeddb", "metadata": {}, "source": [ "## 2. NS372 (Kaupp) - definitions\n", "\n", "Conventional GIAO shieldings for 44 functionals across 8 main-group nuclei, with a\n", "CCSD(T)/pcSseg-3 reference. Kaupp Reduced-Set exclusions (F₃⁻, O₃, BH - multireference\n", "outliers) are applied. Input: the Kaupp NS372 supporting-information spreadsheet." ] }, { "cell_type": "markdown", "id": "34326933", "metadata": {}, "source": [ "## 3. delta22 - definitions\n", "\n", "Gas-phase conventional GIAO shieldings from the delta-22 set: 18 functionals at their\n", "largest available basis (pcSseg-3; plain MP2 only to pcSseg-2), at the PBE0/cc-pVTZ\n", "geometry. Observations are pooled ¹H / ¹³C atom sites across all 22 solutes. There is no\n", "CCSD(T) reference in the file; **DSD-PBEP86 is used as the reference** for delta22 - it\n", "is the highest-rung double-hybrid available in this method set and stands in for CCSD(T)\n", "in the same role. The stored whole-number shieldings are decoded on read." ] }, { "cell_type": "markdown", "id": "eedf9b3d", "metadata": {}, "source": [ "## 4. Load datasets + global colour scale\n", "\n", "Run the loaders, analyse every nucleus, and compute the figure-wide\n", "-log10(1-|r|) maximum used as the colour scale on every correlation matrix\n", "below, for NS372 and delta22 alike." ] }, { "cell_type": "code", "execution_count": null, "id": "4bec0cf4", "metadata": {}, "outputs": [], "source": [ "ns372 = leveling.load_ns372(KAUPP_XLSX)\n", "delta22 = leveling.load_delta22(DELTA22_H5)\n", "\n", "res_ns372 = {nuc: leveling.analyze_nucleus(d['M'], d['methods'], d['ref'])\n", " for nuc, d in ns372.items()}\n", "res_delta22 = {nuc: leveling.analyze_nucleus(d['M'], d['methods'], d['ref'])\n", " for nuc, d in delta22.items()}\n", "\n", "GLOBAL_VMAX = max(leveling.dataset_logr_max(res_ns372), leveling.dataset_logr_max(res_delta22))\n", "print(f'NS372 : {len(ns372)} nuclei')\n", "print(f'delta22: {len(delta22)} nuclei (reference = {leveling.DELTA22_REF})')\n", "print(f'global colour scale: 0 -> {GLOBAL_VMAX} on the -log10(1-|r|) axis')\n", "for nuc, d in ns372.items():\n", " print(f' NS372 {nuc:4s}: {d[\"M\"].shape[0]:4d} mols x {d[\"M\"].shape[1]-1} methods + CCSD(T)')\n", "for nuc, d in delta22.items():\n", " print(f' delta22 {nuc:4s}: {d[\"M\"].shape[0]:4d} sites x {d[\"M\"].shape[1]} methods')" ] }, { "cell_type": "markdown", "id": "667fef3c", "metadata": {}, "source": [ "## 5. NS372 results" ] }, { "cell_type": "markdown", "id": "c5f2cbda", "metadata": {}, "source": [ "### 5.1 Leveling diagnostics - NS372" ] }, { "cell_type": "code", "execution_count": null, "id": "8b9b9449", "metadata": {}, "outputs": [], "source": [ "sum_ns372 = leveling.summarize(ns372, res_ns372)\n", "print('NS372 - leveling diagnostics (CCSD(T) included as a method column):')\n", "sum_ns372.round(5)" ] }, { "cell_type": "markdown", "id": "2b666d04", "metadata": {}, "source": [ "### 5.2 Per-method scaled RMSE vs CCSD(T)\n", "\n", "Scaled RMSE = RMSE of residuals after a per-method linear fit of each method's shieldings\n", "against the CCSD(T) reference (slope and intercept) - the error that survives empirical\n", "linear scaling." ] }, { "cell_type": "code", "execution_count": null, "id": "4020359a", "metadata": {}, "outputs": [], "source": [ "rmse_ns372 = pd.DataFrame({nuc: res_ns372[nuc]['scaled_rmse'] for nuc in res_ns372})\n", "rmse_ns372 = rmse_ns372.drop(index='CCSD(T)', errors='ignore')\n", "rmse_ns372 = rmse_ns372.reindex(ns372['1H']['methods'][:-1]) # family order\n", "print('NS372 - per-method scaled RMSE vs CCSD(T) (ppm):')\n", "rmse_ns372.round(3)" ] }, { "cell_type": "markdown", "id": "ba763232", "metadata": {}, "source": [ "### 5.3 Inter-method correlation matrices - NS372 (one nucleus per file)" ] }, { "cell_type": "code", "execution_count": null, "id": "133a5053", "metadata": {}, "outputs": [], "source": [ "NS372_NUCS = ['1H', '11B', '13C', '15N', '17O', '19F', '31P', '33S']\n", "for nuc in NS372_NUCS:\n", " fams = {nuc: ns372[nuc]['families']}\n", " fig = leveling_plots.plot_corr_matrix('NS372', [nuc], res_ns372, fams,\n", " GLOBAL_VMAX, ref_name='CCSD(T)',\n", " nuc_display=NUC_DISPLAY)\n", " fig.savefig(figure_path(f'si_figure_s03_corr_{nuc}.png'),\n", " dpi=SAVE_DPI, bbox_inches='tight')\n", " plt.show()" ] }, { "cell_type": "markdown", "id": "0fa55480", "metadata": {}, "source": [ "### 5.4 PC1/PC2 structure - NS372 (one nucleus per file)" ] }, { "cell_type": "code", "execution_count": null, "id": "a37b38c6", "metadata": {}, "outputs": [], "source": [ "for nuc in NS372_NUCS:\n", " fams = {nuc: ns372[nuc]['families']}\n", " fig = leveling_plots.plot_pca_pair('NS372', [nuc], res_ns372, fams,\n", " family_colors=FAMILY_COLORS, nuc_display=NUC_DISPLAY,\n", " pca_adjust=PCA_ADJUST, pca_adjust_default=PCA_ADJUST_DEFAULT)\n", " fig.savefig(figure_path(f'si_figure_s03_pca_{nuc}.png'),\n", " dpi=SAVE_DPI, bbox_inches='tight')\n", " plt.show()" ] }, { "cell_type": "markdown", "id": "bc460701", "metadata": {}, "source": [ "### 5.5 delta22 panels (lead the published figure: corr ¹H/¹³C + PCA)\n", "\n", "The same correlation + PCA layout on the delta22 gas-phase set. DSD-PBEP86 is the reference (delta22\n", "has no CCSD(T)); it appears as an ordinary double-hybrid point in the PCA, with no CCSD(T) star." ] }, { "cell_type": "code", "execution_count": null, "id": "74e02974", "metadata": {}, "outputs": [], "source": [ "# delta22 correlation matrices (canonical S3 A = 1H, B = 13C)\n", "for nuc in ['1H', '13C']:\n", " fams = {nuc: delta22[nuc]['families']}\n", " fig = leveling_plots.plot_corr_matrix('delta22', [nuc], res_delta22, fams,\n", " GLOBAL_VMAX, ref_name=leveling.DELTA22_REF,\n", " nuc_display=NUC_DISPLAY)\n", " fig.savefig(figure_path(f'si_figure_s03_delta22_corr_{nuc}.png'),\n", " dpi=SAVE_DPI, bbox_inches='tight')\n", " plt.show()\n", "\n", "# delta22 PCA projection: 1H and 13C stacked in one figure (canonical S3 C)\n", "fams = {nuc: delta22[nuc]['families'] for nuc in ['1H', '13C']}\n", "fig = leveling_plots.plot_pca_pair('delta22', ['1H', '13C'], res_delta22, fams,\n", " family_colors=FAMILY_COLORS, nuc_display=NUC_DISPLAY,\n", " pca_adjust=PCA_ADJUST, pca_adjust_default=PCA_ADJUST_DEFAULT)\n", "fig.savefig(figure_path('si_figure_s03_delta22_pca.png'),\n", " dpi=SAVE_DPI, bbox_inches='tight')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "49825011", "metadata": {}, "source": [ "## 6. Combined summary (both datasets)" ] }, { "cell_type": "code", "execution_count": null, "id": "bffe5ff3", "metadata": {}, "outputs": [], "source": [ "# delta22's per-nucleus diagnostics (same summarize() used for NS372 above), computed here\n", "# too so this combined table is self-contained -- it reuses res_delta22 from the shared load\n", "# cell above, so this is cheap (no new correlation/PCA computation).\n", "sum_delta22 = leveling.summarize(delta22, res_delta22)\n", "\n", "combined = pd.concat([sum_ns372.assign(dataset='NS372'),\n", " sum_delta22.assign(dataset='delta22')], ignore_index=True)\n", "combined = combined[['dataset','nucleus','n_obs','n_methods','r_min','r_median',\n", " 'PC1_pct','PC2_pct','PC3plus_pct','parabola_R2']]\n", "print('Leveling effect - both datasets (analyzed separately):')\n", "print(f' PC1 range : {combined.PC1_pct.min():.2f}% - {combined.PC1_pct.max():.2f}%')\n", "print(f' min pairwise r : {combined.r_min.min():.5f} (worst case, all nuclei)')\n", "print(f' parabola R2 range : {combined.parabola_R2.min():.3f} - {combined.parabola_R2.max():.3f}')\n", "print(f' global colour scale : 0 -> {GLOBAL_VMAX} on -log10(1-|r|)')\n", "combined.round(5)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }