{ "cells": [ { "cell_type": "markdown", "id": "1cec8e8d", "metadata": {}, "source": [ "# Tutorial 06 — FastRelax\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/06_fast_relax.ipynb)\n", "\n", "TMol `fast_relax()` alternates side-chain repacking and minimization under a changing score-function schedule.\n", "\n", "## Learning objectives\n", "\n", "- Prepare a packing palette, move map, and fold forest.\n", "- Run one short repeat and inspect the weight schedule.\n", "- Compare the input, stage endpoints, and accepted result.\n", "\n", "## Before you begin\n", "\n", "- **Prerequisites:** Both [04 — Packing](04_packing_and_mutation_scan.ipynb) and [05 — Minimization](05_minimization_constraints_kinematics.ipynb).\n", "- **Curriculum:** This rejoins the two branches after 03 and completes the core 01 → 06 path. Continue with specialized [07 — Ligands](07_ligand_and_params.ipynb) and [08 — Nucleic acids](08_nucleic_acids.ipynb).\n", "- **Related:** [Optimization workflow](../user_guide/optimization.md) · [FastRelax API](../api/relax.rst)\n", "\n", "TMol `fast_relax()` is Rosetta-inspired, but its scoring, search, and acceptance behavior differ. The short CPU run checks that the API works; it does not establish convergence. The larger batch example requires CUDA." ] }, { "cell_type": "markdown", "id": "c1383b5a", "metadata": {}, "source": [ "## Setup\n", "\n", "The main example uses a checked-in six-residue 1UBQ slice, one repeat, and a two-stage schedule so it remains practical on CPU. The larger ensemble cell is tagged `gpu-only` and is executed by the GPU CI lane. Both are pedagogical smoke tests rather than production relaxation schedules." ] }, { "cell_type": "code", "execution_count": 1, "id": "1eefc8e2", "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([\"tmol/tests/data/cif/1UBQ.cif\"])" ] }, { "cell_type": "code", "execution_count": 2, "id": "609ab896", "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 matplotlib.pyplot as plt\n", "import numpy as np\n", "import pandas as pd\n", "import torch\n", "from IPython.display import display\n", "from biotite.structure.io import load_structure\n", "\n", "import tmol\n", "from tmol.io import pose_stack_from_biotite\n", "from tmol.kinematics import CartesianMoveMap, FoldForest, MoveMap\n", "from tmol.pack import PackerPalette\n", "from tmol.pose import PoseStackBuilder\n", "from tmol.score import ScoreType, beta2016_score_function\n", "from tmol.score.constraint import create_mainchain_coordinate_constraints\n", "\n", "SEED = 20260807\n", "np.random.seed(SEED)\n", "torch.manual_seed(SEED)\n", "warnings.filterwarnings(\n", " \"ignore\", message=r\"Sparse invariant checks are implicitly disabled.*\"\n", ")\n", "device = (\n", " torch.device(\"cuda\", torch.cuda.current_device())\n", " if torch.cuda.is_available()\n", " else torch.device(\"cpu\")\n", ")\n", "\n", "# Strictly a smoke-test budget shared by every minimizer in this tutorial,\n", "# including the GPU batch. Finite output is not evidence of convergence.\n", "TUTORIAL_MAX_ITER = 10\n", "\n", "\n", "def show_table(frame):\n", " try:\n", " from itables import show\n", " except ImportError:\n", " return display(frame)\n", " return show(frame)\n", "\n", "\n", "def score_components_by_pose(pose_stack, score_function):\n", " scorer = score_function.render_whole_pose_scoring_module(pose_stack)\n", " weighted_terms = scorer(\n", " pose_stack.coords, sum_terms=False, apply_weights=True\n", " )\n", " score_types = score_function.all_score_types()\n", " total_scores = weighted_terms.sum(dim=0)\n", " constraint_index = next(\n", " (\n", " index\n", " for index, score_type in enumerate(score_types)\n", " if score_type == ScoreType.constraint\n", " ),\n", " None,\n", " )\n", " constraint_scores = (\n", " torch.zeros_like(total_scores)\n", " if constraint_index is None\n", " else weighted_terms[constraint_index]\n", " )\n", " non_constraint_scores = total_scores - constraint_scores\n", " return [\n", " {\n", " \"non_constraint_score_units\": float(non_constraint_scores[i].detach().cpu()),\n", " \"constraint_score_units\": float(constraint_scores[i].detach().cpu()),\n", " \"reported_total_score_units\": float(total_scores[i].detach().cpu()),\n", " }\n", " for i in range(pose_stack.n_poses)\n", " ]\n", "\n", "\n", "def score_components(pose_stack, score_function):\n", " if pose_stack.n_poses != 1:\n", " raise ValueError(\"Use score_components_by_pose() for batched PoseStacks.\")\n", " return score_components_by_pose(pose_stack, score_function)[0]\n", "\n", "\n", "def common_atom_mask(reference, mobile):\n", " return torch.isfinite(reference.coords).all(dim=-1) & torch.isfinite(\n", " mobile.coords\n", " ).all(dim=-1)\n", "\n", "\n", "def rms_displacement(reference, mobile):\n", " \"\"\"Raw all-atom RMS displacement in the input coordinate frame.\"\"\"\n", " mask = common_atom_mask(reference, mobile)\n", " delta = mobile.coords[mask] - reference.coords[mask]\n", " return float(torch.sqrt(torch.mean(torch.sum(delta * delta, dim=-1))).cpu())\n", "\n", "\n", "def kabsch_aligned_rmsd(reference, mobile):\n", " \"\"\"All-atom RMSD after least-squares rigid-body superposition.\"\"\"\n", " mask = common_atom_mask(reference, mobile)\n", " reference_xyz = reference.coords[mask]\n", " mobile_xyz = mobile.coords[mask]\n", " reference_centered = reference_xyz - reference_xyz.mean(dim=0)\n", " mobile_centered = mobile_xyz - mobile_xyz.mean(dim=0)\n", " covariance = mobile_centered.T @ reference_centered\n", " u, _, vh = torch.linalg.svd(covariance)\n", " handedness = torch.sign(torch.det(u @ vh))\n", " correction = torch.eye(3, device=mobile_xyz.device, dtype=mobile_xyz.dtype)\n", " correction[-1, -1] = handedness\n", " rotation = u @ correction @ vh\n", " delta = mobile_centered @ rotation - reference_centered\n", " return float(torch.sqrt(torch.mean(torch.sum(delta * delta, dim=-1))).cpu())\n", "\n", "\n", "def declared_mainchain_atom_mask(pose_stack):\n", " \"\"\"Select atoms named in each residue type's TMol polymer declaration.\"\"\"\n", " mask = torch.zeros_like(pose_stack.real_atoms)\n", " atom_names = []\n", " seen_atom_names = set()\n", " for pose_index in range(pose_stack.n_poses):\n", " for block_index in range(pose_stack.max_n_blocks):\n", " block_type_index = int(\n", " pose_stack.block_type_ind64[pose_index, block_index].item()\n", " )\n", " if block_type_index < 0:\n", " continue\n", " block_type = pose_stack.packed_block_types.active_block_types[\n", " block_type_index\n", " ]\n", " polymer = block_type.properties.polymer\n", " if polymer is None:\n", " continue\n", " atom_offset = int(\n", " pose_stack.block_coord_offset64[pose_index, block_index].item()\n", " )\n", " for atom_name in polymer.mainchain_atoms:\n", " atom_index = atom_offset + block_type.atom_to_idx[atom_name]\n", " if bool(pose_stack.real_atoms[pose_index, atom_index]):\n", " mask[pose_index, atom_index] = True\n", " if atom_name not in seen_atom_names:\n", " atom_names.append(atom_name)\n", " seen_atom_names.add(atom_name)\n", " return mask, tuple(atom_names)\n", "\n", "\n", "def coordinates_equal(first, second):\n", " if first.coords.shape != second.coords.shape:\n", " return False\n", " first_finite = torch.isfinite(first.coords)\n", " second_finite = torch.isfinite(second.coords)\n", " return (\n", " torch.equal(first.block_type_ind64, second.block_type_ind64)\n", " and torch.equal(first_finite, second_finite)\n", " and torch.equal(first.coords[first_finite], second.coords[second_finite])\n", " )" ] }, { "cell_type": "markdown", "id": "33f473af", "metadata": {}, "source": [ "## Protocol and ramp schedule\n", "\n", "`tmol.fast_relax()` alternates side-chain repacking and minimization while increasing the `fa_rep` weight. A `CartesianMoveMap` selects Cartesian coordinates. A `MoveMap`, `FoldForest`, and custom minimizer select kinematic degrees of freedom.\n", "\n", "The default protocol uses four stages and two repeats. This tutorial uses two stages, one repeat, and ten minimizer iterations so it runs as a smoke test. Finite output shows that the workflow executes; it does not establish convergence.\n", "\n", "Coordinate constraints are attached before relaxation. Each schedule entry scales the starting repulsive and constraint weights, and the final minimization uses full repulsion." ] }, { "cell_type": "code", "execution_count": 3, "id": "e02638f1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "input: 1UBQ.cif\n", "coordinate-restraint atom names: N, CA, C\n", "coordinate-restraint count: 18\n", "coordinate-restraint sigma: 0.5 Å\n", "coordinate-restraint score weight: 1.0\n", "O is not TMol-declared main chain here and is not constrained.\n", "Weight 1.0 is intentional; Rosetta comparisons commonly use 0.5.\n" ] }, { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", "\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n", "\n", "
fa_rep_pack_fracfa_rep_min_fraccst_fracfa_rep_pack_weightfa_rep_min_weightconstraint_weight
0.10.21.00.0550.111.0
1.01.00.00.5500.550.0
\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "repo_root = Path.cwd()\n", "if not (repo_root / \"tmol/tests/data/cif/1UBQ.cif\").exists():\n", " repo_root = Path(tmol.__file__).resolve().parents[1]\n", "cif_path = repo_root / \"tmol\" / \"tests\" / \"data\" / \"cif\" / \"1UBQ.cif\"\n", "atom_array = load_structure(str(cif_path), model=1, include_bonds=True)\n", "protein_slice = atom_array[(atom_array.chain_id == \"A\") & (atom_array.res_id <= 6)]\n", "setup_diagnostics = StringIO()\n", "try:\n", " with redirect_stdout(setup_diagnostics), redirect_stderr(setup_diagnostics):\n", " pose = pose_stack_from_biotite(protein_slice, device, no_optH=True)\n", " relax_start = create_mainchain_coordinate_constraints(pose)\n", "except Exception:\n", " print(setup_diagnostics.getvalue())\n", " raise\n", "\n", "score_function = beta2016_score_function(device)\n", "score_function.set_weight(ScoreType.constraint, 1.0)\n", "fa_rep_start = float(score_function.get_weight(ScoreType.fa_ljrep))\n", "constraint_start = float(score_function.get_weight(ScoreType.constraint))\n", "mainchain_mask, restrained_atom_names = declared_mainchain_atom_mask(relax_start)\n", "CONSTRAINT_SIGMA_A = 0.5\n", "\n", "palette = PackerPalette()\n", "cartesian_move_map = CartesianMoveMap()\n", "fold_forest = FoldForest.reasonable_fold_forest(relax_start)\n", "tiny_schedule = [\n", " {\"fa_rep_pack_frac\": 0.10, \"fa_rep_min_frac\": 0.20, \"cst_frac\": 1.0},\n", " {\"fa_rep_pack_frac\": 1.00, \"fa_rep_min_frac\": 1.00, \"cst_frac\": 0.0},\n", "]\n", "\n", "schedule_table = pd.DataFrame(tiny_schedule)\n", "schedule_table[\"fa_rep_pack_weight\"] = (\n", " schedule_table[\"fa_rep_pack_frac\"] * fa_rep_start\n", ")\n", "schedule_table[\"fa_rep_min_weight\"] = (\n", " schedule_table[\"fa_rep_min_frac\"] * fa_rep_start\n", ")\n", "schedule_table[\"constraint_weight\"] = schedule_table[\"cst_frac\"] * constraint_start\n", "print(\"input:\", cif_path.name)\n", "print(\"coordinate-restraint atom names:\", \", \".join(restrained_atom_names))\n", "print(\"coordinate-restraint count:\", int(mainchain_mask.sum().item()))\n", "print(f\"coordinate-restraint sigma: {CONSTRAINT_SIGMA_A:.1f} Å\")\n", "print(f\"coordinate-restraint score weight: {constraint_start:.1f}\")\n", "print(\"O is not TMol-declared main chain here and is not constrained.\")\n", "print(\"Weight 1.0 is intentional; Rosetta comparisons commonly use 0.5.\")\n", "show_table(schedule_table)" ] }, { "cell_type": "markdown", "id": "7da9eaf1", "metadata": {}, "source": [ "## Run one repeat\n", "\n", "The first stage uses reduced repulsion and the final stage restores full repulsion. The fold forest is passed through the shared protocol API, although the Cartesian minimizer does not use it.\n", "\n", "The custom minimizer records the score immediately before and after each short minimization. These values help inspect the workflow, but they are not a convergence test." ] }, { "cell_type": "code", "execution_count": 4, "id": "cb4874b0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "smoke-test optimizer max_iter: 10 (convergence not assessed)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "accepted final source: repeat 1 endpoint (stage 2)\n" ] }, { "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", "
structureaccepted_fromnon_constraint_score_unitsconstraint_score_unitsreported_total_score_unitsnon_constraint_score_change_unitsconstraint_score_change_unitsreported_total_score_change_unitsfinite_smoke_test_outputconvergence_status
beforenot applicable20.7102340.020.7102340.0000000.00.000000Truenot assessed (10-iteration smoke test)
accepted finalrepeat 1 endpoint (stage 2)1.5906090.01.590609-19.1196250.0-19.119625Truenot assessed (10-iteration smoke test)
\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def tutorial_cart_min(\n", " pose_stack, stage_score_function, *, fold_forest, move_map, verbose\n", "):\n", " del fold_forest\n", " coord_mask = move_map.coord_mask if isinstance(move_map, CartesianMoveMap) else None\n", " return tmol.run_cart_min(\n", " pose_stack,\n", " stage_score_function,\n", " coord_mask=coord_mask,\n", " verbose=verbose,\n", " optimizer_kwargs={\n", " \"max_iter\": TUTORIAL_MAX_ITER,\n", " \"verbose\": verbose,\n", " },\n", " )\n", "\n", "\n", "relax_stage_records = []\n", "\n", "\n", "def recording_cart_min(\n", " pose_stack, stage_score_function, *, fold_forest, move_map, verbose\n", "):\n", " constraint_weight = float(\n", " stage_score_function.get_weight(ScoreType.constraint)\n", " )\n", " objective_before = score_components(\n", " pose_stack, stage_score_function\n", " )[\"reported_total_score_units\"]\n", " minimized = tutorial_cart_min(\n", " pose_stack,\n", " stage_score_function,\n", " fold_forest=fold_forest,\n", " move_map=move_map,\n", " verbose=verbose,\n", " )\n", " objective_after = score_components(\n", " minimized, stage_score_function\n", " )[\"reported_total_score_units\"]\n", " relax_stage_records.append(\n", " {\n", " \"pose\": minimized.clone(),\n", " \"constraint_weight_at_optimization\": constraint_weight,\n", " \"objective_before_min_units\": objective_before,\n", " \"objective_after_min_units\": objective_after,\n", " \"objective_change_during_min_units\": objective_after - objective_before,\n", " }\n", " )\n", " return minimized\n", "\n", "\n", "print(\n", " f\"smoke-test optimizer max_iter: {TUTORIAL_MAX_ITER} \"\n", " \"(convergence not assessed)\"\n", ")\n", "before_components = score_components(relax_start, score_function)\n", "TUTORIAL_NUM_REPEATS = 1\n", "relaxed = tmol.fast_relax(\n", " relax_start,\n", " score_function,\n", " palette,\n", " cartesian_move_map,\n", " fold_forest,\n", " num_repeats=TUTORIAL_NUM_REPEATS,\n", " schedule=tiny_schedule,\n", " ramp_constraints=True,\n", " min_fn=recording_cart_min,\n", " verbose=False,\n", ")\n", "assert torch.isfinite(relaxed.coords[relaxed.real_atoms]).all()\n", "after_components = score_components(relaxed, score_function)\n", "\n", "if coordinates_equal(relaxed, relax_start):\n", " accepted_from = \"input\"\n", "else:\n", " accepted_from = \"unresolved (not an exact recorded repeat endpoint)\"\n", " for repeat_index in range(TUTORIAL_NUM_REPEATS):\n", " record_index = (repeat_index + 1) * len(tiny_schedule) - 1\n", " if coordinates_equal(relaxed, relax_stage_records[record_index][\"pose\"]):\n", " accepted_from = (\n", " f\"repeat {repeat_index + 1} endpoint \"\n", " f\"(stage {record_index + 1})\"\n", " )\n", " break\n", "\n", "score_frame = pd.DataFrame(\n", " [\n", " {\n", " \"structure\": \"before\",\n", " \"accepted_from\": \"not applicable\",\n", " **before_components,\n", " },\n", " {\n", " \"structure\": \"accepted final\",\n", " \"accepted_from\": accepted_from,\n", " **after_components,\n", " },\n", " ]\n", ")\n", "for component in (\n", " \"non_constraint_score_units\",\n", " \"constraint_score_units\",\n", " \"reported_total_score_units\",\n", "):\n", " score_frame[f\"{component.removesuffix('_units')}_change_units\"] = (\n", " score_frame[component] - before_components[component]\n", " )\n", "score_frame[\"finite_smoke_test_output\"] = np.isfinite(\n", " score_frame[\n", " [\n", " \"non_constraint_score_units\",\n", " \"constraint_score_units\",\n", " \"reported_total_score_units\",\n", " ]\n", " ]\n", ").all(axis=1)\n", "score_frame[\"convergence_status\"] = \"not assessed (10-iteration smoke test)\"\n", "print(\"accepted final source:\", accepted_from)\n", "show_table(score_frame)" ] }, { "cell_type": "code", "execution_count": 5, "id": "8e64a6d4", "metadata": { "tags": [ "collapse-code" ] }, "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", "\n", "\n", "\n", "
framelabelaccepted_fromconstraint_weight_at_optimizationobjective_before_min_unitsobjective_after_min_unitsobjective_change_during_min_unitsconstraint_weight_for_posthoc_rescoreraw_RMS_displacement_from_input_AKabsch_RMSD_from_input_Anon_constraint_score_unitsconstraint_score_unitsreported_total_score_units
0inputNaNNaNNaNNaN0.00.0000009.758238e-0720.7102360.020.710236
1stage 11.08.0549021.290334-6.7645680.01.5270991.469361e+004.3828520.04.382852
2stage 20.03.5003891.590609-1.9097800.01.4742091.402359e+001.5906090.01.590609
3accepted finalrepeat 1 endpoint (stage 2)NaNNaNNaNNaN0.01.4742091.402359e+001.5906090.01.590609
\n", "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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RIbD9kRkOMZuT8a43s1VIuHs7IwDBw6gZs2aeRGTwTGRKJxSFcGnmy7ij3OPoVYB33erhXdqFTd2WGRkbFNEhsP2RGQ4xm5PxrjezdVempmZoUyZMoaOhUiHRq3CN11qoms9LygCjN54AZvPvvjhhURERESUP3KVSLRt2xbr1683dCxEGWjUKsx+twY+qF8CigCfbLqI304/MnZYREREREVergZbJyUl4cMPP8TmzZtRrlw5qFQqnffz+8nX9GpTq1WY2akaTNQqrPZ9gHGbLyFFEfRoUMLYoREREREVWblKJGJjY7UPinv8+LFBAyLKjFqtwpfvVIWJRoXlx+9j0pbLSFUU9G5UytihERERERVJuUoktm7dauAwiLKnUqnwRfsqMFGr8OtRP3z+51WkKIL+jUsbOzQiIiKiIodTNFCholKpMOntyhjarCwAYNq2a/j1yD0jR0VERERU9OTqjkT37t1f+P6GDRtyFQyRPlQqFca1rggTtQo/HriDmTuvI0URbXJBRERERHkvV3ckbGxsdH6srKzw+PFjbNy4Ebl4LAVRjqlUKox9qyJGt6wAAJiz6wZ+3H/byFERERERFR25uiOxZMmSTMu/++473Lx586UCIsqJj1uWh4lGhW9238S3e28hRRGMalk+w0xiRERERGRYuUoksjJw4EBUqVLFkKskytbw5uXSnjfx9w3M238bqYpg7FsVmEwQERER5SGDDrZ++PAhEhISDLlKIr182LQsPmtXGQCw4OAdzN51g93siIiIiPJQru5ITJ06NUNZREQEfv/9d3Tu3PllYyLKlUFvlIGJWoWp265h0eF7SEkVfNauMu9MEBEREeWBXCUS+/bty1Dm6OiIUaNGYcSIES8dFFFu9WtcGhqNGp9vvYKlx/yQqgimdKjCZIKIiIjIwHKVSBw7dszQcRAZTO+GJWGiVmHSlstY8c99pCgKvuxYDWo1kwkiIiIiQ+ED6eiV9EH9Evj6vRpQqYA1vg8xactlKArHTBAREREZChMJemW9X88b33WtCbUK2HD6EcZtvoRUJhNEREREBsFEgl5pnWt74ftutaBRq7DprD8+/f0ikwkiIiIiA2AiQa+8d2oVx/zutWGiVuGP848xauMFpKQqxg6LiIiIqFBjIkFFQrsanljQow5MNSpsu/gEIzecRzKTCSIiIqJcy1UiISJYsmQJ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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "
\n", " \n", "
\n", " \n", "
\n", "
not a minimization endpoint; post-hoc non-constraint 20.710; constraint 0.000; raw/aligned 0.000/0.000 Å
\n", "
\n", "

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

\n", "
\n", "\n", "
\n", "
\n", "
optimized cst weight 1.000; min objective change -6.765; post-hoc non-constraint 4.383; constraint 0.000; raw/aligned 1.527/1.469 Å
\n", "
\n", "

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

\n", "
\n", "\n", "
\n", "
\n", "
optimized cst weight 0.000; min objective change -1.910; post-hoc non-constraint 1.591; constraint 0.000; raw/aligned 1.474/1.402 Å
\n", "
\n", "

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

\n", "
\n", "\n", "
\n", "
\n", "
not a minimization endpoint; post-hoc non-constraint 1.591; constraint 0.000; raw/aligned 1.474/1.402 Å; accepted from repeat 1 endpoint (stage 2)
\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": [ "trajectory_structures = {\"input\": relax_start}\n", "stage_metadata = {\n", " \"input\": {\n", " \"constraint_weight_at_optimization\": np.nan,\n", " \"objective_before_min_units\": np.nan,\n", " \"objective_after_min_units\": np.nan,\n", " \"objective_change_during_min_units\": np.nan,\n", " }\n", "}\n", "for stage_index, record in enumerate(relax_stage_records):\n", " label = f\"stage {stage_index + 1}\"\n", " trajectory_structures[label] = record[\"pose\"]\n", " stage_metadata[label] = record\n", "trajectory_structures[\"accepted final\"] = relaxed\n", "stage_metadata[\"accepted final\"] = {\n", " \"constraint_weight_at_optimization\": np.nan,\n", " \"objective_before_min_units\": np.nan,\n", " \"objective_after_min_units\": np.nan,\n", " \"objective_change_during_min_units\": np.nan,\n", "}\n", "\n", "# fast_relax restores the starting constraint weight before returning. These\n", "# scores are therefore a common post-hoc decomposition, not each stage's\n", "# historical optimization objective.\n", "posthoc_constraint_weight = float(\n", " score_function.get_weight(ScoreType.constraint)\n", ")\n", "trajectory_rows = []\n", "trajectory_notes = {}\n", "for frame_index, (label, frame_pose) in enumerate(trajectory_structures.items()):\n", " components = score_components(frame_pose, score_function)\n", " metadata = stage_metadata[label]\n", " raw_displacement = rms_displacement(relax_start, frame_pose)\n", " aligned_rmsd = kabsch_aligned_rmsd(relax_start, frame_pose)\n", " trajectory_rows.append(\n", " {\n", " \"frame\": frame_index,\n", " \"label\": label,\n", " \"accepted_from\": accepted_from if label == \"accepted final\" else \"\",\n", " \"constraint_weight_at_optimization\": metadata[\n", " \"constraint_weight_at_optimization\"\n", " ],\n", " \"objective_before_min_units\": metadata[\n", " \"objective_before_min_units\"\n", " ],\n", " \"objective_after_min_units\": metadata[\n", " \"objective_after_min_units\"\n", " ],\n", " \"objective_change_during_min_units\": metadata[\n", " \"objective_change_during_min_units\"\n", " ],\n", " \"constraint_weight_for_posthoc_rescore\": posthoc_constraint_weight,\n", " \"raw_RMS_displacement_from_input_A\": raw_displacement,\n", " \"Kabsch_RMSD_from_input_A\": aligned_rmsd,\n", " **components,\n", " }\n", " )\n", " optimized_weight = metadata[\"constraint_weight_at_optimization\"]\n", " optimized_text = (\n", " \"not a minimization endpoint\"\n", " if np.isnan(optimized_weight)\n", " else (\n", " f\"optimized cst weight {optimized_weight:.3f}; min objective change \"\n", " f\"{metadata['objective_change_during_min_units']:+.3f}\"\n", " )\n", " )\n", " acceptance_text = (\n", " f\"; accepted from {accepted_from}\" if label == \"accepted final\" else \"\"\n", " )\n", " trajectory_notes[label] = (\n", " f\"{optimized_text}; post-hoc non-constraint \"\n", " f\"{components['non_constraint_score_units']:.3f}; \"\n", " f\"constraint {components['constraint_score_units']:.3f}; \"\n", " f\"raw/aligned {raw_displacement:.3f}/{aligned_rmsd:.3f} Å\"\n", " f\"{acceptance_text}\"\n", " )\n", "trajectory_frame = pd.DataFrame(trajectory_rows)\n", "show_table(trajectory_frame)\n", "\n", "fig, ax = plt.subplots(figsize=(8, 4))\n", "ax.plot(\n", " trajectory_frame[\"frame\"],\n", " trajectory_frame[\"non_constraint_score_units\"],\n", " marker=\"o\",\n", " label=\"post-hoc non-constraint score\",\n", ")\n", "ax.plot(\n", " trajectory_frame[\"frame\"],\n", " trajectory_frame[\"constraint_score_units\"],\n", " marker=\"o\",\n", " label=\"post-hoc constraint score\",\n", ")\n", "ax.set(\n", " xticks=trajectory_frame[\"frame\"],\n", " xticklabels=trajectory_frame[\"label\"],\n", " ylabel=\"weighted score units\",\n", " title=\"FastRelax endpoints rescored at restored constraint weight\",\n", ")\n", "ax.tick_params(axis=\"x\", rotation=20)\n", "ax.grid(alpha=0.3)\n", "ax.legend()\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "display(\n", " tmol.switchable_view(\n", " trajectory_structures,\n", " notes=trajectory_notes,\n", " )\n", ")" ] }, { "cell_type": "markdown", "id": "59eda836", "metadata": {}, "source": [ "**Expected observations.** The non-constraint and constraint components, score changes, and structural metrics must be finite. “Non-constraint” is the exact description: it includes every weighted beta2016 term except the coordinate-constraint term, including empirical/statistical contributions. All reported values are weighted TMol score units, not kcal/mol. The raw metric retains coordinate-frame motion; Kabsch RMSD removes the best rigid-body superposition.\n", "\n", "`fast_relax()` restores the starting constraint weight before returning. The table records the weight and objective change present during each stage's minimization separately from the restored weight used for the common post-hoc score decomposition. In particular, the final stage is optimized with `cst_frac=0`, but its displayed constraint penalty is recalculated afterward at full starting weight; it diagnoses restraint violation and is not part of that stage's optimization objective. The structure switcher records minimization endpoints for each pack/min stage and the final accept-to-best result; it does not contain LBFGS line-search evaluations or internal packing trajectories.\n", "\n", "TMol's accept-best pool is **input-inclusive**: it initializes the best pose from the unrelaxed input and, after each complete schedule repeat, accepts that repeat endpoint only when its score is strictly lower. It does not accept among every intermediate stage. Consequently, a one-repeat run can legitimately return the exact input, and `accepted_from` explicitly distinguishes that case from the recorded repeat endpoint. This differs from Rosetta's usual FastRelax comparison among relaxed repeat outputs, where the unrelaxed input is not normally retained as an accept-best candidate.\n", "\n", "There is an important custom-schedule caveat: the initial best score is captured with the weights present at protocol start, whereas `accept_best` scores a repeat endpoint with the weights left by the final schedule step. The tiny schedule here ends at full repulsion and zero constraint weight, and its restraints are built from the input coordinates, which masks that mismatch for this demonstration. For a custom schedule or initially violated restraints, rescore the input and repeat endpoints under one fixed weight set before interpreting which structure is better.\n", "\n", "The `score_frame` and trajectory decomposition above are **matched-weight rescoring**: input, stage endpoints, and the accepted result are all evaluated after `fast_relax()` restores the same starting score-function weights. Historical per-stage objectives remain separate columns because those stages used different ramp weights. Compare structures only within one declared weight set; never subtract a low-repulsion stage objective from a full-repulsion endpoint and call the result an improvement.\n", "\n", "A tiny pedagogical schedule is not guaranteed to improve every input. Large distortions, NaNs, or a missing ligand/residue are failures, not expected stochastic variation. Conversely, finite output or a favorable objective change under `max_iter=10` is only smoke-test evidence; convergence is not assessed.\n", "\n", "## Convenience minimizers and precision boundary\n", "\n", "`fast_relax` calls a custom minimizer as `min_fn(pose_stack, sfxn, *, fold_forest, move_map, verbose)`. The adapter below uses the supported top-level `tmol.run_kin_min(pose_stack, sfxn, ff, mm, ...)` signature and the same tutorial iteration limit. To use it, supply a `MoveMap` (not a `CartesianMoveMap`) and pass `min_fn=kinematic_min_fn` to `fast_relax`.\n", "\n", "Direct packing operates on float32 coordinates. The FastRelax wrappers make the precision boundary explicit: when given a float64 pose, each packing stage receives a temporary float32 view, then the packed coordinates are converted back to float64 before minimization and the returned pose preserves float64. The main run above asserts this behavior. For direct `pack_rotamers()` calls, keep float32 coordinates unless that API's contract changes.\n", "\n", "`kin_dtype=torch.float64` promotes the kinematic DOFs and inverse/forward-kinematics calculation. Reconstructed coordinates use the pose's coordinate dtype for scoring, so pass a float64 pose as well when the intended kinematic minimization path is double precision.\n", "\n", "The kinematic adapter is defined but not invoked because doing so would duplicate the full packing schedule with a different minimizer. Notebook 05 executes both Cartesian and kinematic minimization directly; use that comparison before enabling the more expensive FastRelax variant here." ] }, { "cell_type": "code", "execution_count": 6, "id": "0c2cb59a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FastRelax input/output dtype: torch.float32 → torch.float32\n" ] } ], "source": [ "def kinematic_min_fn(\n", " pose_stack, score_function, *, fold_forest, move_map, verbose\n", "):\n", " return tmol.run_kin_min(\n", " pose_stack,\n", " score_function,\n", " fold_forest,\n", " move_map,\n", " optimizer_kwargs={\n", " \"max_iter\": TUTORIAL_MAX_ITER,\n", " \"verbose\": verbose,\n", " },\n", " verbose=verbose,\n", " kin_dtype=torch.float64,\n", " )\n", "\n", "\n", "kinematic_move_map = MoveMap.from_pose_stack(relax_start)\n", "kinematic_move_map.move_all_named_torsions = True\n", "kinematic_move_map.move_all_jumps = False\n", "kinematic_move_map.move_all_root_jumps = False\n", "print(\n", " \"FastRelax input/output dtype:\",\n", " relax_start.coords.dtype,\n", " \"→\",\n", " relaxed.coords.dtype,\n", ")\n", "\n", "# Optional kinematic form using the same public protocol (not run here):\n", "# kin_relaxed = tmol.fast_relax(\n", "# relax_start,\n", "# score_function,\n", "# palette,\n", "# kinematic_move_map,\n", "# fold_forest,\n", "# num_repeats=1,\n", "# schedule=tiny_schedule,\n", "# min_fn=kinematic_min_fn,\n", "# )" ] }, { "cell_type": "markdown", "id": "d7a51e7d", "metadata": {}, "source": [ "## Relax an ensemble in one GPU batch\n", "\n", "The single-structure call above and the batched call below use the same constrained protocol: each perturbed input receives main-chain coordinate constraints, the ensemble score function starts with the same nonzero constraint weight, the same two-stage schedule is used, and every minimization uses `TUTORIAL_MAX_ITER`. Four reproducibly perturbed conformations are assembled into one `PoseStack`, relaxed together, scored with the same constraint/non-constraint decomposition, and split after the batched protocol for per-member metrics and the labeled structure switcher. This exposes the batch dimension to GPU kernels; measure it against four serial calls on the target GPU before choosing a production batch size.\n", "\n", "Constraints are constructed after perturbation, so each ensemble member is restrained to its own starting TMol-declared N/CA/C coordinates, exactly as the single structure is restrained to its own start; O is not constrained. The results report per-member score changes, raw displacement, Kabsch-aligned RMSD, and whether accept-best retained that member's input or the recorded repeat endpoint. The cell is marked `gpu-only`: the GPU CI lane executes it, while the hosted CPU docs build preserves its checked-in output. The Colab button remains the way to rerun it on another GPU." ] }, { "cell_type": "code", "execution_count": null, "id": "11ebd429", "metadata": { "tags": [ "gpu-only" ] }, "outputs": [], "source": [ "#| tags: [gpu-only]\n", "if device.type != \"cuda\":\n", " print(\"Skipped: open this notebook in a Colab GPU runtime to run batch relax.\")\n", "else:\n", " ensemble_members = {}\n", " for member_index, noise_scale in enumerate((0.00, 0.01, 0.02, 0.03)):\n", " member = pose.clone()\n", " generator = torch.Generator(device=device).manual_seed(SEED + member_index)\n", " noise = torch.randn(\n", " member.coords.shape,\n", " generator=generator,\n", " device=device,\n", " dtype=member.coords.dtype,\n", " )\n", " member.coords[member.real_atoms] += noise[member.real_atoms] * noise_scale\n", " ensemble_members[f\"input {member_index}\"] = member\n", "\n", " ensemble = PoseStackBuilder.from_poses(\n", " list(ensemble_members.values()), device\n", " )\n", " ensemble = create_mainchain_coordinate_constraints(ensemble)\n", " ensemble_sfxn = beta2016_score_function(device)\n", " ensemble_sfxn.set_weight(ScoreType.constraint, constraint_start)\n", " ensemble_before = score_components_by_pose(ensemble, ensemble_sfxn)\n", " ensemble_stage_records = []\n", "\n", " def recording_ensemble_cart_min(\n", " pose_stack, stage_score_function, *, fold_forest, move_map, verbose\n", " ):\n", " minimized = tutorial_cart_min(\n", " pose_stack,\n", " stage_score_function,\n", " fold_forest=fold_forest,\n", " move_map=move_map,\n", " verbose=verbose,\n", " )\n", " ensemble_stage_records.append(minimized.clone())\n", " return minimized\n", "\n", " relaxed_ensemble = tmol.fast_relax(\n", " ensemble,\n", " ensemble_sfxn,\n", " PackerPalette(),\n", " CartesianMoveMap(),\n", " FoldForest.reasonable_fold_forest(ensemble),\n", " num_repeats=1,\n", " schedule=tiny_schedule,\n", " ramp_constraints=True,\n", " min_fn=recording_ensemble_cart_min,\n", " verbose=False,\n", " )\n", " ensemble_after = score_components_by_pose(\n", " relaxed_ensemble, ensemble_sfxn\n", " )\n", "\n", " ensemble_rows = []\n", " ensemble_accepted_from = {}\n", " for member_index in range(ensemble.n_poses):\n", " before_pose = ensemble.split(member_index)\n", " after_pose = relaxed_ensemble.split(member_index)\n", " endpoint_pose = ensemble_stage_records[-1].split(member_index)\n", " if coordinates_equal(after_pose, before_pose):\n", " member_accepted_from = \"input\"\n", " elif coordinates_equal(after_pose, endpoint_pose):\n", " member_accepted_from = \"repeat 1 endpoint (stage 2)\"\n", " else:\n", " member_accepted_from = \"unresolved\"\n", " ensemble_accepted_from[member_index] = member_accepted_from\n", " raw_after = rms_displacement(before_pose, after_pose)\n", " aligned_after = kabsch_aligned_rmsd(before_pose, after_pose)\n", " before = ensemble_before[member_index]\n", " after = ensemble_after[member_index]\n", " ensemble_rows.extend(\n", " [\n", " {\n", " \"member\": member_index,\n", " \"structure\": \"before\",\n", " \"accepted_from\": \"not applicable\",\n", " \"raw_RMS_displacement_A\": 0.0,\n", " \"Kabsch_RMSD_A\": 0.0,\n", " \"non_constraint_score_change_units\": 0.0,\n", " \"constraint_score_change_units\": 0.0,\n", " \"objective_change_units\": 0.0,\n", " **before,\n", " },\n", " {\n", " \"member\": member_index,\n", " \"structure\": \"accepted final\",\n", " \"accepted_from\": member_accepted_from,\n", " \"raw_RMS_displacement_A\": raw_after,\n", " \"Kabsch_RMSD_A\": aligned_after,\n", " \"non_constraint_score_change_units\": (\n", " after[\"non_constraint_score_units\"]\n", " - before[\"non_constraint_score_units\"]\n", " ),\n", " \"constraint_score_change_units\": (\n", " after[\"constraint_score_units\"]\n", " - before[\"constraint_score_units\"]\n", " ),\n", " \"objective_change_units\": (\n", " after[\"reported_total_score_units\"]\n", " - before[\"reported_total_score_units\"]\n", " ),\n", " **after,\n", " },\n", " ]\n", " )\n", " ensemble_frame = pd.DataFrame(ensemble_rows)\n", " ensemble_frame[\"finite_smoke_test_output\"] = np.isfinite(\n", " ensemble_frame[\n", " [\n", " \"reported_total_score_units\",\n", " \"raw_RMS_displacement_A\",\n", " \"Kabsch_RMSD_A\",\n", " ]\n", " ]\n", " ).all(axis=1)\n", " ensemble_frame[\"convergence_status\"] = (\n", " \"not assessed (10-iteration smoke test)\"\n", " )\n", " show_table(ensemble_frame)\n", "\n", " comparison_structures = {}\n", " comparison_notes = {}\n", " for member_index in range(ensemble.n_poses):\n", " before_label = f\"member {member_index} — before\"\n", " after_label = f\"member {member_index} — after\"\n", " comparison_structures[before_label] = ensemble.split(member_index)\n", " comparison_structures[after_label] = relaxed_ensemble.split(member_index)\n", " before = ensemble_before[member_index]\n", " after = ensemble_after[member_index]\n", " comparison_notes[before_label] = (\n", " f\"non-constraint {before['non_constraint_score_units']:.3f}; \"\n", " f\"constraint {before['constraint_score_units']:.3f} score units; \"\n", " \"raw/aligned 0.000/0.000 Å\"\n", " )\n", " comparison_notes[after_label] = (\n", " f\"non-constraint {after['non_constraint_score_units']:.3f}; \"\n", " f\"constraint {after['constraint_score_units']:.3f}; \"\n", " f\"raw/aligned \"\n", " f\"{rms_displacement(ensemble.split(member_index), relaxed_ensemble.split(member_index)):.3f}/\"\n", " f\"{kabsch_aligned_rmsd(ensemble.split(member_index), relaxed_ensemble.split(member_index)):.3f} Å; \"\n", " f\"accepted from {ensemble_accepted_from[member_index]}\"\n", " )\n", " display(\n", " tmol.switchable_view(\n", " comparison_structures,\n", " notes=comparison_notes,\n", " )\n", " )" ] }, { "cell_type": "markdown", "id": "d1e153e3", "metadata": {}, "source": [ "## Rosetta comparison\n", "\n", "TMol `fast_relax()` is a Rosetta-inspired, batched refinement subset—not an exact Rosetta FastRelax port. Both repeat packing and minimization under a changing score-function schedule, but their score functions, sampling, minimizers, acceptance behavior, trajectories, and outputs differ.\n", "\n", "TMol defaults to Cartesian minimization, whereas Rosetta FastRelax commonly uses torsional movement unless configured otherwise. TMol's accept-best pool also includes the unrelaxed input; Rosetta repeat selection does not normally retain that input as a candidate. Use this notebook to learn the TMol API, and consult the [Rosetta FastRelax mover documentation](https://docs.rosettacommons.org/docs/latest/scripting_documentation/RosettaScripts/Movers/movers_pages/FastRelaxMover) for Rosetta protocol options and production guidance.\n", "\n", "## Next: add noncanonical chemistry\n", "\n", "Continue to [07 — Ligands and parameter files](07_ligand_and_params.ipynb) to prepare ligand chemistry and refine a local pocket." ] }, { "cell_type": "markdown", "id": "c0f57fb1", "metadata": {}, "source": [ "## Exercises\n", "\n", "1. Replace the tiny schedule with `DEFAULT_RELAX_SCHEDULE` and compare runtime and score.\n", "2. Keep constraints active in the final step and compare Cα displacement.\n", "3. Pass `kinematic_min_fn` with the prepared `kinematic_move_map` and compare Cartesian and kinematic results.\n", "4. Restrict packing with a `PackerTask` block mask, such as `disable_packing_by_block_mask()`.\n", "5. Batch two small poses, then rescore the input and repeat endpoints under one fixed weight set before interpreting accept-to-best behavior per pose." ] }, { "cell_type": "markdown", "id": "624597cb", "metadata": {}, "source": [ "## References\n", "\n", "- [Rosetta-to-TMol crosswalk](rosetta_crosswalk.md)\n", "- [Rosetta Relax tutorial](https://docs.rosettacommons.org/demos/latest/tutorials/Relax_Tutorial/Relax)\n", "- [Rosetta FastRelax implementation](https://github.com/RosettaCommons/rosetta/blob/main/source/src/protocols/relax/FastRelax.cc)\n", "- [PyRosetta packing, design, and regional relax](https://nbviewer.org/github/RosettaCommons/PyRosetta.notebooks/blob/master/notebooks/06.02-Packing-design-and-regional-relax.ipynb)\n", "- [Current PyRosetta notebook curriculum](https://rosettacommons.github.io/PyRosetta.notebooks/)" ] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "T4" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }