TMol How-To Guide#
A single, growing reference notebook for working with TMol. Each section is self-contained and can be run independently once the Setup cell below has run.
1. Basics#
Load a structure from the RCSB, inspect it, change it, save it, and build batches of poses.
Setup#
In Colab, choose a GPU runtime and Run all. For local execution, follow the installation guide. This section fetches structures live from the RCSB, so no local fixture files are required.
The first call into any of TMol’s native routines in a fresh environment JIT-compiles kernels and can take several minutes; later calls reuse the compiled cache and are fast.
Wheel install, imports, seeding, and device selection are not themselves part of what this notebook teaches, so they live in notebook_setup.py and are pulled in with a single call.
[1]:
try:
import google.colab # noqa: F401
except ImportError:
IN_COLAB = False
else:
IN_COLAB = True
if IN_COLAB:
from urllib.request import urlretrieve
urlretrieve(
"https://raw.githubusercontent.com/RosettaCommons/tmol/"
"main/docs/tutorial/notebook_setup.py",
"notebook_setup.py",
)
from notebook_setup import * # noqa: F401,F403
device, param_db = setup()
Environment variable CCD_MIRROR_PATH not set. Will not be able to use function requiring this variable. To set it you may:
(1) add the line 'export VAR_NAME=path/to/variable' to your .bashrc or .zshrc file
(2) set it in your current shell with 'export VAR_NAME=path/to/variable'
(3) write it to a .env file in the root of the atomworks.io repository
Environment variable PDB_MIRROR_PATH not set. Will not be able to use function requiring this variable. To set it you may:
(1) add the line 'export VAR_NAME=path/to/variable' to your .bashrc or .zshrc file
(2) set it in your current shell with 'export VAR_NAME=path/to/variable'
(3) write it to a .env file in the root of the atomworks.io repository
Loading Structures#
Download a CIF file from RCSB#
We use 1UBQ (ubiquitin) as the example structure for this section. fetch_cif downloads the raw mmCIF text; it is not written to disk here, just held in memory.
[2]:
PDB_ID = "1UBQ"
ubq_cif_text = fetch_cif(PDB_ID)
print(f"downloaded {PDB_ID}")
downloaded 1UBQ
Load a PoseStack from a CIF#
A PoseStack represents a batch of molecular systems (in this case, just a single structure - ubiquitin). PoseStacks are optimized for compactness for efficient processing on the GPU. pose_stack_from_file accepts a path or an in-memory buffer.
[3]:
pose_stack = pose_stack_from_file(io.StringIO(ubq_cif_text), device)
print(f"n_poses={pose_stack.n_poses}, max_n_blocks={pose_stack.max_n_blocks}")
viewer = tmol.view(pose_stack)
viewer.show()
n_poses=1, max_n_blocks=76
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
3Dmol.js failed to load for some reason. Please check your browser console for error messages.
Load a PoseStack with ligands#
To load a PoseStack with ligands, you must specify prepare_ligands=True while loading the structure. Without this flag, any ligands will be silently ommitted from the resulting PoseStack. More details about this in section 4 (TODO: add a forward link/reference to the ligand section when we re-combine the notebooks).
[4]:
# Fetch a new PDB with ligands present
LIGAND_PDB_ID = "3PTB"
ligand_cif_text = fetch_cif(LIGAND_PDB_ID)
# Load the PDB with prepare_ligands=True
ligand_pose_stack = pose_stack_from_file(io.StringIO(ligand_cif_text), device, prepare_ligands=True)
ligand_atom_mask = res_mask_to_coord_mask(ligand_pose_stack, tmol.get_ligand_mask(ligand_pose_stack))
viewer = tmol.view(ligand_pose_stack, highlighted=ligand_atom_mask[ligand_pose_stack.real_atoms])
viewer.show()
==============================
*** Open Babel Warning in WriteMolecule
No 2D or 3D coordinates exist. Stereochemical information will be stored using an Open Babel extension. To generate 2D or 3D coordinates instead use --gen2D or --gen3D.
/home/runner/work/tmol/tmol/tmol/io/_pose_stack_from_biotite.py:1401: UserWarning: Renumbering chain A of the output AtomArray: biotite's CIF writer does not support numbering that decreases within a chain
warnings.warn(
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
3Dmol.js failed to load for some reason. Please check your browser console for error messages.
Stacking PoseStacks#
tmol is optimized to work best on batches of structures.
You can batch multiple structures into a single PoseStack, even if they are different structures.
[5]:
multi_pdb_stack = PoseStackBuilder.from_poses([pose_stack, ligand_pose_stack], device)
print(
f"n_poses={multi_pdb_stack.n_poses}, "
f"max_n_blocks={multi_pdb_stack.max_n_blocks} "
f"(members had {pose_stack.max_n_blocks} and {ligand_pose_stack.max_n_blocks} blocks)"
)
n_poses=2, max_n_blocks=225 (members had 76 and 225 blocks)
Stacking multiple copies of a Pose#
The same function, given [pose_stack] * n, builds a batch of n independent copies. This is the standard way to collect several independent stochastic outcomes from one starting structure, such as running FastRelax or the packer several times with different random seeds.
[6]:
n_copies = 4
repeat_stack = PoseStackBuilder.from_poses([pose_stack] * n_copies, device)
print(f"n_poses={repeat_stack.n_poses}, max_n_blocks={repeat_stack.max_n_blocks}")
n_poses=4, max_n_blocks=76
Query the PoseStack for info#
Get all sequences#
[7]:
sequence = tmol.get_sequence(pose_stack)
print(sequence)
MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG
Get ligands (3-letter code + SMILES)#
tmol.get_ligand_mask flags every ligand (or other non-polymer residue, such as an ion) block in a PoseStack. tmol.ligand.smiles_from_pose_stack then derives a canonical SMILES string for one of those residues directly from the PoseStack’s own coordinates and connectivity.
[8]:
ligand_cif_text = fetch_cif("3PTB")
ligand_pose_stack = pose_stack_from_file(
io.StringIO(ligand_cif_text), device, param_db=param_db, prepare_ligands=True
)
ligand_mask = tmol.get_ligand_mask(ligand_pose_stack)
for block in torch.nonzero(ligand_mask[0]).flatten().tolist():
code = ligand_pose_stack.block_type(0, block).name3
smiles = smiles_from_pose_stack(ligand_pose_stack, 0, block)
print(f"{code}: block {block}, SMILES={smiles}")
CA: block 223, SMILES=[Ca-2]
BEN: block 224, SMILES=NC(=[NH2+])c1ccccc1
Query phi/psi angles using pdb_info#
TODO - should phi/psi (and other named-torsion queries) support looking a residue up by its original PDB numbering directly, instead of resolving a block index as a separate step first?
[9]:
query_block = tmol.block_for_residue_label(pose_stack, 0, "A", 43)
block_type = pose_stack.block_type(0, query_block)
print(f"chain A residue 43 -> block {query_block} ({block_type.name3})")
print("available torsions:", tmol.get_torsion_names(pose_stack, 0, query_block))
phi = tmol.get_named_torsions(pose_stack, 0, query_block, "phi")
psi = tmol.get_named_torsions(pose_stack, 0, query_block, "psi")
print(f"phi={phi:.1f} deg, psi={psi:.1f} deg")
chain A residue 43 -> block 42 (LEU)
available torsions: ['phi', 'psi', 'omega', 'chi1', 'chi2']
phi=-103.6 deg, psi=130.2 deg
Stacked pose querying#
[10]:
# Some functions can work on the whole pose stack
tmol.get_sequence(multi_pdb_stack)
[10]:
'MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDYNIQKESTLHLVLRLRGG'
[11]:
# Others you must extract the individual poses
for pose_index in range(multi_pdb_stack.n_poses):
single = multi_pdb_stack.split(pose_index)
single_sequence = tmol.get_sequence(single)
print(f"pose {pose_index}: {len(single_sequence)} blocks, starts {single_sequence[:20]}...")
pose 0: 76 blocks, starts MQIFVKTLTGKTITLEVEPS...
pose 1: 225 blocks, starts IVGGYTCGANTVPYQVSLNS...
Setting basic info in the PoseStack#
Set a residue’s sidechain chi angles#
Torsion values can be set directly by name using tmol.set_named_torsions. This returns a new PoseStack with the updated coordinates from the moves.
[12]:
# block 42 (LEU, found above as chain A residue 43) has a chi1 torsion
before_chi1 = tmol.get_named_torsions(pose_stack, 0, query_block, "chi1")
rotated_pose_stack = tmol.set_named_torsions(
pose_stack, 0, query_block, "chi1", before_chi1 + 30.0
)
after_chi1 = tmol.get_named_torsions(rotated_pose_stack, 0, query_block, "chi1")
print(
f"block {query_block} ({pose_stack.block_type(0, query_block).name3}) chi1: "
f"{before_chi1:.1f} -> {after_chi1:.1f} deg"
)
display(tmol.overlay_view(
{"original": pose_stack, "chi1 rotated": rotated_pose_stack},
notes={"original": f"chi1={before_chi1:.1f} deg", "chi1 rotated": f"chi1={after_chi1:.1f} deg"},
))
block 42 (LEU) chi1: -68.3 -> -38.3 deg
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
Modify a residue’s identity#
tmol.mutate_residue wraps the packer-based mutation above into a single call: it restricts packing to one block, narrows its allowed identity to the requested one, and runs pack_rotamers with a Dunbrack sampler for the new identity. The input PoseStack is left unchanged.
[13]:
mutated_pose_stack = tmol.mutate_residue(
pose_stack, 0, query_block, "ALA", param_db=param_db
)
print(
f"block {query_block}: "
f"{pose_stack.block_type(0, query_block).name3} -> "
f"{mutated_pose_stack.block_type(0, query_block).name3}"
)
display(tmol.overlay_view(
{"original": pose_stack, "mutated": mutated_pose_stack},
notes={
"original": f"block {query_block}: {pose_stack.block_type(0, query_block).name3}",
"mutated": f"block {query_block}: {mutated_pose_stack.block_type(0, query_block).name3}",
},
))
block 42: LEU -> ALA
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
Mutate to match a target sequence#
tmol.mutate_to_sequence does the same for a whole one-letter sequence at once: every position whose identity already matches is left untouched, and every differing position is redesigned together in a single packing run.
[14]:
target_sequence = list(tmol.get_sequence(pose_stack))
target_sequence[query_block] = "G"
target_sequence = "".join(target_sequence)
resequenced_pose_stack = tmol.mutate_to_sequence(
pose_stack, 0, target_sequence, param_db=param_db
)
print(f"block {query_block}: {pose_stack.block_type(0, query_block).name3} -> "
f"{resequenced_pose_stack.block_type(0, query_block).name3}")
print("sequence now matches target:", tmol.get_sequence(resequenced_pose_stack) == target_sequence)
display(tmol.overlay_view(
{"original": pose_stack, "resequenced": resequenced_pose_stack},
notes={
"original": f"block {query_block}: {pose_stack.block_type(0, query_block).name3}",
"resequenced": f"block {query_block}: {resequenced_pose_stack.block_type(0, query_block).name3}",
},
))
block 42: LEU -> GLY
sequence now matches target: True
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
Writing outputs - TODO#
TODO - this section is using the old pdb writer. There are functions for writing through biotite/atomworks, but these have the limitation of only being able to output structures with the same chemical identity. So they are not a general solution for a write-pdb/cif one liner. How do we want to resolve this?
file_from_pose_stack saves a PoseStack to a PDB file. tmol.view renders a PoseStack (or a Biotite structure, or raw PDB text) in an interactive py3Dmol viewer.
[15]:
import tempfile
from pathlib import Path
output_path = Path(tempfile.gettempdir()) / f"{PDB_ID.lower()}_mutated.pdb"
file_from_pose_stack(mutated_pose_stack, str(output_path))
print(f"wrote {output_path}")
viewer = tmol.view(mutated_pose_stack)
viewer.show()
wrote /tmp/1ubq_mutated.pdb
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
/home/runner/work/tmol/tmol/.venv/lib/python3.12/site-packages/biotite/structure/io/pdb/file.py:629: DeprecationWarning: The chararray class is deprecated and will be removed in a future release. Use an ndarray with a string or bytes dtype instead.
record = np.char.array(np.where(array.hetero, "HETATM", "ATOM"))
3Dmol.js failed to load for some reason. Please check your browser console for error messages.