{ "cells": [ { "cell_type": "markdown", "id": "892511b5", "metadata": {}, "source": [ "# Case Study 10 — Ligand Pose Sensitivity and Local Rescue\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/uw-ipd/tmol/blob/master/docs/tutorial/10_ligand_pose_sensitivity.ipynb)\n", "\n", "A bound ligand can look plausible while making poor local contacts. This case study creates controlled rigid-body decoys of one crystallographic ligand, scores every pose in one batch, and locally minimizes three diagnostic states.\n", "\n", "## Biological question\n", "\n", "Does the one-complex TMol interaction score distinguish the deposited ligand placement from controlled displacements, and can short local minimization rescue selected poses?\n", "\n", "## Learning objectives\n", "\n", "- Reuse authoritative ligand chemistry through one build context.\n", "- Build and score a matched ligand-decoy batch.\n", "- Relate interaction score to ligand heavy-atom displacement.\n", "- Minimize a local ligand/pocket shell in one batched call.\n", "- Separate pose sensitivity from docking and binding-affinity claims.\n", "\n", "## Before you begin\n", "\n", "Complete [07 — Ligands and Parameter Files](07_ligand_and_params.ipynb) first. This case study reuses its pinned ADA/LG1 fixture and chemistry but keeps the experimental question separate from parameter-file mechanics.\n" ] }, { "cell_type": "markdown", "id": "7ff5f3cf", "metadata": {}, "source": [ "## Setup\n", "\n", "The checked-in CIF and matching `.tmol` file are the authoritative coordinate and chemical inputs. The notebook runs on CPU or CUDA, fixes every random seed, and performs no live structure download.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "dbec29b1", "metadata": {}, "outputs": [], "source": [ "try:\n", " import google.colab # noqa: F401\n", "except ImportError:\n", " IN_COLAB = False\n", "else:\n", " IN_COLAB = True\n", "\n", "if IN_COLAB:\n", " from urllib.request import urlopen\n", "\n", " exec(\n", " urlopen(\n", " \"https://raw.githubusercontent.com/uw-ipd/tmol/\"\n", " \"master/docs/tutorial/colab_setup.py\"\n", " ).read(),\n", " globals(),\n", " )\n", " setup_colab(\n", " [\n", " \"tmol/tests/data/protein_ligand_test/ada.tmol.nomin.cif\",\n", " \"tmol/tests/data/protein_ligand_test/ada.xtal-lig.mmff94.tmol\",\n", " ]\n", " )" ] }, { "cell_type": "code", "execution_count": 2, "id": "cc968d37", "metadata": {}, "outputs": [], "source": [ "from contextlib import redirect_stderr, redirect_stdout\n", "from io import StringIO\n", "from pathlib import Path\n", "import warnings\n", "\n", "import biotite.structure.io\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import torch\n", "from IPython.display import display\n", "\n", "import tmol\n", "from tmol.database import ParameterDatabase\n", "from tmol.io import build_context_from_biotite, pose_stack_from_biotite\n", "from tmol.ligand import inject_params_file\n", "from tmol.ops import build_sidechain_coord_mask, res_mask_to_coord_mask\n", "from tmol.optimization import run_cart_min\n", "from tmol.pose import PoseStackBuilder\n", "from tmol.score import beta2016_score_function\n", "\n", "SEED = 20260810\n", "np.random.seed(SEED)\n", "torch.manual_seed(SEED)\n", "if torch.cuda.is_available():\n", " torch.cuda.manual_seed_all(SEED)\n", "warnings.filterwarnings(\n", " \"ignore\", message=r\"Sparse invariant checks are implicitly disabled.*\"\n", ")\n", "\n", "device = (\n", " torch.device(\"cuda\", torch.cuda.current_device())\n", " if torch.cuda.is_available()\n", " else torch.device(\"cpu\")\n", ")\n", "LIGAND_NAME = \"LG1\"\n", "\n", "\n", "def show_table(frame):\n", " \"\"\"Display a sortable table when available, with a pandas fallback.\"\"\"\n", " try:\n", " from itables import show\n", " except ImportError:\n", " return display(frame)\n", " return show(frame)\n", "\n", "\n", "def block_mask_for_name3(pose, name3):\n", " \"\"\"Select blocks with one residue name across a PoseStack.\"\"\"\n", " mask = torch.zeros_like(pose.block_type_ind, dtype=torch.bool)\n", " for pose_index in range(pose.n_poses):\n", " for block_index in range(pose.max_n_blocks):\n", " if int(pose.block_type_ind64[pose_index, block_index]) < 0:\n", " continue\n", " mask[pose_index, block_index] = (\n", " pose.block_type(pose_index, block_index).name3 == name3\n", " )\n", " return mask\n", "\n", "\n", "def heavy_atom_mask_for_blocks(pose, block_mask):\n", " \"\"\"Expand selected blocks to their non-hydrogen coordinate atoms.\"\"\"\n", " mask = torch.zeros_like(pose.real_atoms)\n", " for pose_index, block_index in torch.nonzero(block_mask, as_tuple=False).tolist():\n", " block_type_index = int(pose.block_type_ind64[pose_index, block_index].item())\n", " block_type = pose.block_type(pose_index, block_index)\n", " offset = int(pose.block_coord_offset64[pose_index, block_index])\n", " n_atoms = len(block_type.atoms)\n", " is_hydrogen = pose.packed_block_types.atom_is_hydrogen[\n", " block_type_index, :n_atoms\n", " ].bool()\n", " mask[pose_index, offset : offset + n_atoms] = ~is_hydrogen\n", " return mask & pose.real_atoms\n", "\n", "\n", "def ligand_protein_interactions(pose, score_function):\n", " \"\"\"Return both-orientation weighted ligand–protein scores per pose.\"\"\"\n", " ligand = block_mask_for_name3(pose, LIGAND_NAME)\n", " protein = (pose.block_type_ind64 >= 0) & ~ligand\n", " pair_mask = (ligand[:, :, None] & protein[:, None, :]) | (\n", " protein[:, :, None] & ligand[:, None, :]\n", " )\n", " scorer = score_function.render_block_pair_scoring_module(pose)\n", " with torch.no_grad():\n", " weighted = scorer(\n", " pose.coords, sum_terms=False, apply_weights=True\n", " ).sum(dim=0)\n", " return (weighted * pair_mask).sum(dim=(1, 2))\n", "\n", "\n", "def ligand_rmsd_from_native(pose, native_heavy_coords):\n", " \"\"\"Measure ligand heavy-atom RMSD in the fixed protein coordinate frame.\"\"\"\n", " ligand = block_mask_for_name3(pose, LIGAND_NAME)\n", " heavy = heavy_atom_mask_for_blocks(pose, ligand)\n", " values = []\n", " for pose_index in range(pose.n_poses):\n", " delta = pose.coords[pose_index, heavy[pose_index]] - native_heavy_coords\n", " values.append(torch.sqrt(torch.mean(torch.sum(delta * delta, dim=-1))))\n", " return torch.stack(values)" ] }, { "cell_type": "markdown", "id": "824d24e2", "metadata": {}, "source": [ "## Build one ligand-aware context\n", "\n", "The pose and score function use the same extended parameter database. Hydrogen optimization is disabled because the experiment isolates controlled rigid-body ligand placement and short local Cartesian refinement; every compared state starts from the same prepared coordinates and chemistry.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "b42f443f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "device: cpu\n", "coordinate input: ada.tmol.nomin.cif\n", "authoritative chemistry: ada.xtal-lig.mmff94.tmol\n", "ligand heavy atoms: 19\n" ] } ], "source": [ "repo_root = Path.cwd()\n", "data_dir = repo_root / \"tmol\" / \"tests\" / \"data\" / \"protein_ligand_test\"\n", "if not (data_dir / \"ada.tmol.nomin.cif\").exists():\n", " repo_root = Path(tmol.__file__).resolve().parents[1]\n", " data_dir = repo_root / \"tmol\" / \"tests\" / \"data\" / \"protein_ligand_test\"\n", "\n", "complex_path = data_dir / \"ada.tmol.nomin.cif\"\n", "params_path = data_dir / \"ada.xtal-lig.mmff94.tmol\"\n", "parameter_database = inject_params_file(ParameterDatabase.get_default(), params_path)\n", "atom_array = biotite.structure.io.load_structure(\n", " str(complex_path), model=1, include_bonds=True\n", ")\n", "\n", "diagnostics = StringIO()\n", "try:\n", " with redirect_stdout(diagnostics), redirect_stderr(diagnostics):\n", " build_context = build_context_from_biotite(\n", " atom_array,\n", " device,\n", " param_db=parameter_database,\n", " prepare_ligands=False,\n", " )\n", " native_pose = pose_stack_from_biotite(\n", " atom_array,\n", " device,\n", " context=build_context,\n", " no_optH=True,\n", " )\n", "except Exception:\n", " print(diagnostics.getvalue())\n", " raise\n", "\n", "score_function = beta2016_score_function(\n", " device, param_db=build_context.parameter_database\n", ")\n", "native_ligand = block_mask_for_name3(native_pose, LIGAND_NAME)\n", "if int(native_ligand.sum().item()) != 1:\n", " raise RuntimeError(\"Expected exactly one LG1 ligand block\")\n", "native_ligand_coords = res_mask_to_coord_mask(native_pose, native_ligand)\n", "native_heavy_mask = heavy_atom_mask_for_blocks(native_pose, native_ligand)\n", "native_heavy_coords = native_pose.coords[native_heavy_mask]\n", "\n", "print(f\"device: {device}\")\n", "print(f\"coordinate input: {complex_path.name}\")\n", "print(f\"authoritative chemistry: {params_path.name}\")\n", "print(\"ligand heavy atoms:\", int(native_heavy_mask.sum().item()))" ] }, { "cell_type": "markdown", "id": "601cd2e7", "metadata": {}, "source": [ "## Generate a matched decoy series\n", "\n", "Every decoy preserves the protein and the ligand's internal geometry. Only the ligand receives a declared rotation about its centroid and translation in the protein frame. These seven states are sensitivity probes, not samples from a docking search distribution.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "1a9239ed", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
pose_indexstaterotation_degreestranslation_Aligand_heavy_atom_RMSD_Aligand_protein_interaction_scorewhole_pose_score
0deposited0.00.0000000.00000012.211265954.375000
1small rotation15.00.2500000.832808607.7517701549.915527
2small shift-20.00.5000001.193737415.8389591358.002686
3mixed 130.00.7905691.7478931741.3051762683.468994
4mixed 2-45.01.1456442.6729551377.4923102319.656982
5large shift60.01.5000003.4025802660.2473143602.409912
6far decoy90.02.0615534.7883923613.1816414555.344238
\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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7tw5lYvBNOlDRERERBWfTAghSvqiqVOn4vLly/j666/RunVr5K8iMDAQW7ZswW+//abzQCuC1NRU2NjYICUlpcwv4oqLi4Ojo6PejUl5EzFf+oX50i/Ml35hvvRLwXylp6e/khpHV7QaQrBnzx6cOHECtWvXVmlv1aoVhgwZopPAiIiIiIjU0eq/R/Hx8ahatSqAvKvX8ikUCuTm5uomMiIiIiIiNbQqYBs3bowjR44AUC1g//vf/8LHx0c3kRERERERqaHVEIKFCxdiwIABuHTpEgBg9erV+PPPP3Hs2DEcO3ZMpwESERERERWk1RnYLl264MCBAwgJCYGtrS3mz5+P7OxsHD9+HG3bttV1jEREREREEq0v4urfvz/atGmj43CIiIiIiIqn1RnYQYMGQalU6joWIiIiIqKX0qqArVOnDm7cuKHrWIiIiIiIXkqrIQRTpkzBkCFDsGzZMtSvX1/lrlgA4OzsrJPgiIiIiIhepFUBO27cOABAz5491S7X4uZeREREREQa0aqAvX37tq7jICIiIiLSiFYFbL169XQdBxERERGRRrQqYPPdu3cPt2/fhhAC9evXR+3atXUVFxERERGRWloVsCkpKRg1ahT27dsHuTxvIgOlUol+/fphy5YtsLGx0WmQRERERET5tJpGa8qUKbh//z7OnDmDrKwsZGVl4cyZM7h37x6mTp2q6xiJiIiIiCRanYE9cOAAzp07B09PT6nNz88PO3bsgJ+fn86CIyIiIiJ6kVZnYJ89ewZ7e/tC7XZ2dsjMzCx1UERERERERdGqgPX19cXcuXORnZ0ttSkUCsyZMwe+vr46C46IiIiI6EVaDSFYuXIlOnfujP3796NJkyYAgNDQUMjlchw5ckSnARIRERERFaRVAdukSRPcvXsXW7duxc2bNyGTydCrVy+MGDECVlZWuo6RiIiIiEii9TywVlZW+Oijj3QZCxERERHRS2k1BjY1NRW//vprofZff/0VqamppQ6KiIiIiKgoWhWwn3zyCTIyMgq1p6enY8aMGaUOioiIiIioKFoVsPv27cM777xTqP2dd97Bvn37Sh0UEREREVFRtCpglUolEhMTC7UnJiaqTK1FRERERKRrWhWwHTt2xKeffor09HSpLS0tDdOmTUOHDh10FhwRERER0Yu0moXgm2++gb+/P1xdXeHl5QUhBEJCQmBl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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "
\n", " \n", "
\n", " \n", "
\n", "
ligand RMSD 0.00 Å; interaction 12.21
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 0.83 Å; interaction 607.75
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 1.19 Å; interaction 415.84
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 1.75 Å; interaction 1741.31
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 2.67 Å; interaction 1377.49
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 3.40 Å; interaction 2660.25
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
ligand RMSD 4.79 Å; interaction 3613.18
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "decoy_specs = [\n", " (\"deposited\", 0.0, (0.00, 0.00, 0.00)),\n", " (\"small rotation\", 15.0, (0.25, 0.00, 0.00)),\n", " (\"small shift\", -20.0, (0.00, 0.50, 0.00)),\n", " (\"mixed 1\", 30.0, (0.75, 0.25, 0.00)),\n", " (\"mixed 2\", -45.0, (1.00, 0.50, 0.25)),\n", " (\"large shift\", 60.0, (1.50, 0.00, 0.00)),\n", " (\"far decoy\", 90.0, (2.00, 0.50, 0.00)),\n", "]\n", "ligand_xyz = native_pose.coords[native_ligand_coords]\n", "ligand_center = ligand_xyz.mean(dim=0)\n", "decoy_poses = []\n", "for _, angle_degrees, translation_xyz in decoy_specs:\n", " decoy = native_pose.clone()\n", " angle = torch.as_tensor(\n", " np.deg2rad(angle_degrees),\n", " device=device,\n", " dtype=decoy.coords.dtype,\n", " )\n", " rotation = torch.eye(3, device=device, dtype=decoy.coords.dtype)\n", " rotation[0, 0] = torch.cos(angle)\n", " rotation[0, 1] = -torch.sin(angle)\n", " rotation[1, 0] = torch.sin(angle)\n", " rotation[1, 1] = torch.cos(angle)\n", " translation = torch.as_tensor(\n", " translation_xyz, device=device, dtype=decoy.coords.dtype\n", " )\n", " decoy.coords[native_ligand_coords] = (\n", " (ligand_xyz - ligand_center) @ rotation.T + ligand_center + translation\n", " )\n", " decoy_poses.append(decoy)\n", "\n", "decoy_batch = PoseStackBuilder.from_poses(decoy_poses, device)\n", "interaction_scores = ligand_protein_interactions(decoy_batch, score_function).detach()\n", "total_scorer = score_function.render_whole_pose_scoring_module(decoy_batch)\n", "with torch.no_grad():\n", " total_scores = total_scorer(decoy_batch.coords).detach()\n", "ligand_rmsd = ligand_rmsd_from_native(decoy_batch, native_heavy_coords).detach()\n", "\n", "decoy_frame = pd.DataFrame(\n", " [\n", " {\n", " \"pose_index\": pose_index,\n", " \"state\": label,\n", " \"rotation_degrees\": angle,\n", " \"translation_A\": float(np.linalg.norm(translation)),\n", " \"ligand_heavy_atom_RMSD_A\": float(ligand_rmsd[pose_index].cpu()),\n", " \"ligand_protein_interaction_score\": float(\n", " interaction_scores[pose_index].cpu()\n", " ),\n", " \"whole_pose_score\": float(total_scores[pose_index].cpu()),\n", " }\n", " for pose_index, (label, angle, translation) in enumerate(decoy_specs)\n", " ]\n", ")\n", "show_table(decoy_frame)\n", "\n", "fig, axis = plt.subplots(figsize=(7, 4.5))\n", "axis.scatter(\n", " decoy_frame[\"ligand_heavy_atom_RMSD_A\"],\n", " decoy_frame[\"ligand_protein_interaction_score\"],\n", " color=\"#3b82f6\",\n", ")\n", "for row in decoy_frame.itertuples():\n", " axis.annotate(\n", " row.state,\n", " (row.ligand_heavy_atom_RMSD_A, row.ligand_protein_interaction_score),\n", " xytext=(4, 4),\n", " textcoords=\"offset points\",\n", " fontsize=8,\n", " )\n", "axis.set(\n", " xlabel=\"ligand heavy-atom RMSD from deposited pose (Å)\",\n", " ylabel=\"weighted ligand–protein interaction score\",\n", " title=\"Controlled pose sensitivity in one batched score call\",\n", ")\n", "axis.grid(alpha=0.3)\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "display(\n", " tmol.switchable_view(\n", " {\n", " label: decoy_batch.split(index)\n", " for index, (label, _, _) in enumerate(decoy_specs)\n", " },\n", " notes={\n", " row.state: (\n", " f\"ligand RMSD {row.ligand_heavy_atom_RMSD_A:.2f} Å; \"\n", " f\"interaction {row.ligand_protein_interaction_score:.2f}\"\n", " )\n", " for row in decoy_frame.itertuples()\n", " },\n", " )\n", ")" ] }, { "cell_type": "markdown", "id": "e579a94d", "metadata": {}, "source": [ "**Expected observations.** The deposited pose has zero displacement by construction. Increasing displacement generally creates steric or solvation penalties in this controlled series, but monotonicity is not guaranteed for arbitrary transformations or complexes. The interaction quantity combines both block-matrix orientations within one bound complex; it is not a binding free energy or ΔΔG.\n", "\n", "## Locally minimize three diagnostic states\n", "\n", "The deposited pose, best-scoring non-deposited pose, and worst-scoring non-deposited pose are selected without manual cherry-picking. They are assembled into one batch. Every ligand atom and protein side-chain atom initially within 5 Å of that pose's ligand may move; protein main-chain atoms remain fixed. A short shared iteration budget is a local response diagnostic, not a converged docking protocol.\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "3cafb9cd", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", "\n", "\n", "
statemovable_atomsinteraction_beforeinteraction_afterinteraction_changewhole_pose_beforewhole_pose_afterwhole_pose_changeligand_RMSD_before_Aligand_RMSD_after_Aoptimizer_budget
deposited19912.211265-20.625813-32.837078954.374939903.116699-51.2582400.0000000.19897215-iteration smoke test; convergence not assessed
small shift198415.838959-7.526464-423.3654171358.002319924.598877-433.4034421.1937370.90071315-iteration smoke test; convergence not assessed
far decoy2133613.18164159.484619-3553.6970214555.3442381346.806152-3208.5380864.7883924.34262015-iteration smoke test; convergence not assessed
\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "
\n", " \n", "
\n", " \n", "
\n", "
interaction 12.21; ligand RMSD 0.00 Å
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
interaction -20.63; ligand RMSD 0.20 Å; whole-pose Δ -51.26
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
interaction 415.84; ligand RMSD 1.19 Å
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
interaction -7.53; ligand RMSD 0.90 Å; whole-pose Δ -433.40
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
interaction 3613.18; ligand RMSD 4.79 Å
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
interaction 59.48; ligand RMSD 4.34 Å; whole-pose Δ -3208.54
\n", "
\n", "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "\n", "
\n", "
\n", "
\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "best_non_native = 1 + int(torch.argmin(interaction_scores[1:]).item())\n", "worst_non_native = 1 + int(torch.argmax(interaction_scores[1:]).item())\n", "selected_indices = [0, best_non_native, worst_non_native]\n", "if len(set(selected_indices)) != 3:\n", " raise RuntimeError(\"Expected three distinct diagnostic states\")\n", "\n", "selected = PoseStackBuilder.from_poses(\n", " [decoy_batch.split(index) for index in selected_indices], device\n", ")\n", "selected_labels = [decoy_specs[index][0] for index in selected_indices]\n", "selected_ligand = block_mask_for_name3(selected, LIGAND_NAME)\n", "selected_ligand_coords = res_mask_to_coord_mask(selected, selected_ligand)\n", "selected_sidechains = build_sidechain_coord_mask(selected)\n", "near_ligand = torch.zeros_like(selected.real_atoms)\n", "for pose_index in range(selected.n_poses):\n", " ligand_coords = selected.coords[pose_index, selected_ligand_coords[pose_index]]\n", " distances = (\n", " torch.cdist(selected.coords[pose_index].nan_to_num(), ligand_coords)\n", " .min(dim=1)\n", " .values\n", " )\n", " near_ligand[pose_index] = (\n", " (distances <= 5.0)\n", " & selected_sidechains[pose_index]\n", " & selected.real_atoms[pose_index]\n", " )\n", "movable = selected_ligand_coords | near_ligand\n", "\n", "refinement_scorer = score_function.render_whole_pose_scoring_module(selected)\n", "with torch.no_grad():\n", " whole_pose_before = refinement_scorer(selected.coords).detach()\n", "interaction_before = ligand_protein_interactions(selected, score_function).detach()\n", "rmsd_before = ligand_rmsd_from_native(selected, native_heavy_coords).detach()\n", "refined = run_cart_min(\n", " selected,\n", " score_function,\n", " coord_mask=movable,\n", " optimizer_kwargs={\"max_iter\": 15},\n", ")\n", "if not bool(torch.isfinite(refined.coords[refined.real_atoms]).all()):\n", " raise RuntimeError(\"Local refinement produced non-finite coordinates\")\n", "with torch.no_grad():\n", " whole_pose_after = refinement_scorer(refined.coords).detach()\n", "interaction_after = ligand_protein_interactions(refined, score_function).detach()\n", "rmsd_after = ligand_rmsd_from_native(refined, native_heavy_coords).detach()\n", "\n", "refinement_frame = pd.DataFrame(\n", " [\n", " {\n", " \"state\": label,\n", " \"movable_atoms\": int(movable[pose_index].sum().item()),\n", " \"interaction_before\": float(interaction_before[pose_index].cpu()),\n", " \"interaction_after\": float(interaction_after[pose_index].cpu()),\n", " \"interaction_change\": float(\n", " (interaction_after[pose_index] - interaction_before[pose_index]).cpu()\n", " ),\n", " \"whole_pose_before\": float(whole_pose_before[pose_index].cpu()),\n", " \"whole_pose_after\": float(whole_pose_after[pose_index].cpu()),\n", " \"whole_pose_change\": float(\n", " (whole_pose_after[pose_index] - whole_pose_before[pose_index]).cpu()\n", " ),\n", " \"ligand_RMSD_before_A\": float(rmsd_before[pose_index].cpu()),\n", " \"ligand_RMSD_after_A\": float(rmsd_after[pose_index].cpu()),\n", " \"optimizer_budget\": \"15-iteration smoke test; convergence not assessed\",\n", " }\n", " for pose_index, label in enumerate(selected_labels)\n", " ]\n", ")\n", "show_table(refinement_frame)\n", "\n", "viewer_states = {}\n", "viewer_notes = {}\n", "for pose_index, label in enumerate(selected_labels):\n", " before_label = f\"{label} — before\"\n", " after_label = f\"{label} — locally minimized\"\n", " viewer_states[before_label] = selected.split(pose_index)\n", " viewer_states[after_label] = refined.split(pose_index)\n", " viewer_notes[before_label] = (\n", " f\"interaction {float(interaction_before[pose_index].cpu()):.2f}; \"\n", " f\"ligand RMSD {float(rmsd_before[pose_index].cpu()):.2f} Å\"\n", " )\n", " viewer_notes[after_label] = (\n", " f\"interaction {float(interaction_after[pose_index].cpu()):.2f}; \"\n", " f\"ligand RMSD {float(rmsd_after[pose_index].cpu()):.2f} Å; \"\n", " f\"whole-pose Δ {float((whole_pose_after[pose_index] - whole_pose_before[pose_index]).cpu()):+.2f}\"\n", " )\n", "display(tmol.switchable_view(viewer_states, notes=viewer_notes))" ] }, { "cell_type": "markdown", "id": "1a440519", "metadata": {}, "source": [ "**Expected observations.** Local minimization should produce finite coordinates and usually reduces the local objective, but it need not recover the deposited placement from a distant decoy. A better interaction score after refinement does not establish a correct pose: the calculation has no global search, decoy prior, separated state, solvent correction, entropy, or experimental calibration.\n", "\n", "## Rosetta and PyRosetta comparison\n", "\n", "RosettaLigand and GALigandDock provide global sampling and protocol machinery that TMol does not reproduce here. This notebook instead isolates a lower-level question familiar from docking analysis: whether a fixed score function responds sensibly to controlled pose displacement and short local relaxation. See the [Rosetta ligand-docking tutorial](https://docs.rosettacommons.org/demos/latest/tutorials/ligand_docking/ligand_docking_tutorial) and the [Rosetta-to-TMol crosswalk](rosetta_crosswalk.md) for the capability boundary.\n", "\n", "## Exercises\n", "\n", "1. Add rotations around the other two principal axes without changing the native member.\n", "2. Increase the local minimizer budget and check whether score and geometry stabilize.\n", "3. Repeat the selected-state refinement with several pocket cutoffs.\n", "4. Decompose the ligand–protein interaction into weighted score terms.\n", "5. Construct independent rigid transformations before examining scores, then report rank correlation rather than selecting transformations after inspection.\n", "\n", "## References\n", "\n", "- [Tutorial 07 — Ligands and Parameter Files](07_ligand_and_params.ipynb)\n", "- [TMol ligand workflow](../user_guide/ligands.md)\n", "- [Rosetta ligand preparation tutorial](https://docs.rosettacommons.org/demos/latest/tutorials/prepare_ligand/prepare_ligand_tutorial)\n", "- [Rosetta ligand docking tutorial](https://docs.rosettacommons.org/demos/latest/tutorials/ligand_docking/ligand_docking_tutorial)\n" ] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "T4" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }