Tutorial 01 — Working with TMol#

Open In Colab

This tutorial introduces PoseStack, TMol’s tensor-backed molecular representation. You will load 1UBQ from mmCIF, inspect its blocks and labels, select atoms, and write structures.

Learning objectives#

  • Choose a PyTorch device and load a structure.

  • Inspect blocks, coordinates, and author labels.

  • Write one pose or a batch of poses.

Before you begin#

Use mmCIF when possible because it preserves richer metadata and chemical bonds than PDB. The examples run on CPU; CUDA is used automatically when available.

Setup#

Imports, reproducibility, device selection, and fixture discovery live under this heading so the documentation can collapse setup details. The input is checked into the repository; no network access is used.

[1]:
try:
    import google.colab  # noqa: F401
except ImportError:
    IN_COLAB = False
else:
    IN_COLAB = True

if IN_COLAB:
    from urllib.request import urlopen

    exec(
        urlopen(
            "https://raw.githubusercontent.com/uw-ipd/tmol/"
            "master/docs/tutorial/colab_setup.py"
        ).read(),
        globals(),
    )
    setup_colab(["tmol/tests/data/cif/1UBQ.cif"])
[2]:
from collections import Counter
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO
from pathlib import Path
import tempfile
import warnings

import numpy as np
import pandas as pd
import torch
from IPython.display import display
from biotite.structure import AtomArray
from biotite.structure.io import load_structure
from biotite.structure.io.pdb import PDBFile

import tmol
from tmol.database import ParameterDatabase
from tmol.io import biotite_from_pose_stack, pose_stack_from_biotite
from tmol.io import write_pose_stack_pdb
from tmol.pose import PoseStackBuilder

SEED = 20260807
np.random.seed(SEED)
torch.manual_seed(SEED)
if torch.cuda.is_available():
    torch.cuda.manual_seed_all(SEED)

device = (
    torch.device("cuda", torch.cuda.current_device())
    if torch.cuda.is_available()
    else torch.device("cpu")
)
repo_root = Path.cwd()
if not (repo_root / "tmol/tests/data/cif/1UBQ.cif").exists():
    repo_root = Path(tmol.__file__).resolve().parents[1]
cif_path = repo_root / "tmol/tests/data/cif/1UBQ.cif"

atom_array = load_structure(
    str(cif_path),
    model=1,
    include_bonds=True,
    extra_fields=["occupancy", "b_factor"],
)
assert isinstance(atom_array, AtomArray)

param_db = ParameterDatabase.get_default()
# Structure I/O may emit diagnostics while rebuilding missing atoms or handling
# unrecognized residues. Keep them out of the tutorial unless conversion fails.
pose_diagnostics = StringIO()
try:
    with redirect_stdout(pose_diagnostics), redirect_stderr(pose_diagnostics):
        pose_stack, build_context = pose_stack_from_biotite(
            atom_array,
            torch_device=device,
            param_db=param_db,
            no_optH=True,
            return_context=True,
        )
except Exception:
    print(pose_diagnostics.getvalue())
    raise


def show_table(frame):
    """Use sortable tables in rendered docs, with a pandas fallback."""
    try:
        from itables import show
    except ImportError:
        return display(frame)
    return show(frame)


environment_frame = pd.DataFrame(
    [
        {"component": "TMol", "version": tmol.__version__},
        {"component": "PyTorch", "version": torch.__version__},
        {"component": "device", "version": str(device)},
        {"component": "input", "version": cif_path.name},
    ]
)
show_table(environment_frame)
print(f"input atoms={atom_array.array_length()}")
component version
TMol0.1.54
PyTorch2.14.0+cpu
devicecpu
input1UBQ.cif
input atoms=660

From chemistry to coordinates#

ParameterDatabase is the immutable source of chemical and scoring parameters. A PackedBlockTypes object packs the residue types needed by a system onto one device. Each residue-like unit is a block, and PoseStack stores one or more poses as padded, contiguous tensors over those blocks.

A PoseStack lives on one torch.device; build its PackedBlockTypes and score function on that same device. A batch of N structures is one PoseStack with n_poses=N, not N separate Rosetta Pose objects.

Biotite conversion maps the deposited atoms into TMol’s chemical model. During that process TMol detects geometrically compatible disulfides, resolves supported HIS protonation/tautomer variants from the supplied atoms, chooses terminal variants, and builds supported missing atoms. These are chemical-model decisions in the new PoseStack; they do not mutate the deposited atom_array.

The returned build context contains the database, residue-type set, canonical ordering, and packed block types used for conversion. Passing it back as context=build_context reuses that structure-independent chemistry for another compatible input while still rebuilding per-structure coordinates and labels. The round-trip section below demonstrates this explicitly.

Preparation and no_optH decision guide#

  • Use the default no_optH=False before interpreting all-atom scores, especially hydrogen bonds: TMol builds supported missing atoms, samples supported proton chis/NHQ alternatives, and optimizes hydrogen placement as part of conversion.

  • Use no_optH=True for I/O round trips, geometry plumbing, or controlled benchmarks where hydrogen optimization is intentionally outside the experiment. Hydrogens are then built in ideal kinematic positions but are not optimized; do not silently treat resulting hbond values as hydrogen-prepared scores.

  • For deposited ligands or custom chemistry, preserve authoritative CIF/MOL2 bonds and charges and use a validated preparation path. no_optH is not a substitute for ligand parameterization.

  • Record the choice with every score comparison. Changing hydrogen preparation changes the molecular model, not merely runtime.

This notebook uses no_optH=True because it evaluates coordinate and label round trips rather than hbond energies. Tutorials 03 and 04 use optimized hydrogen coordinates for score interpretation; tutorials that deliberately retain deposited or prepared hydrogens state that limitation.

The comparison below reports deposited and TMol-built counts, author-label matches, and reasons exposed by the current APIs, such as excluded water or an unsupported atom label. A matching label supports coordinate comparison but does not prove retained-versus-constructed atom provenance, which the public conversion API does not expose.

[3]:
pbt = pose_stack.packed_block_types
real_blocks = pose_stack.block_type_ind64[0] >= 0
block_type_indices = pose_stack.block_type_ind64[0, real_blocks].detach().cpu().tolist()
block_names = [pbt.active_block_types[i].name for i in block_type_indices]

shape_table = pd.DataFrame(
    [
        ("coords", tuple(pose_stack.coords.shape), str(pose_stack.coords.dtype)),
        ("block_coord_offset", tuple(pose_stack.block_coord_offset.shape), str(pose_stack.block_coord_offset.dtype)),
        ("block_type_ind", tuple(pose_stack.block_type_ind.shape), str(pose_stack.block_type_ind.dtype)),
        ("chain_id", tuple(pose_stack.chain_id.shape), str(pose_stack.chain_id.dtype)),
        ("real_atoms", tuple(pose_stack.real_atoms.shape), str(pose_stack.real_atoms.dtype)),
    ],
    columns=["field", "shape", "dtype"],
)
show_table(shape_table)
print(
    f"n_poses={pose_stack.n_poses}, max_n_blocks={pose_stack.max_n_blocks}, "
    f"max_n_pose_atoms={pose_stack.max_n_pose_atoms}"
)
print("first five block types:", block_names[:5])
print("PackedBlockTypes device:", pbt.device)
print(
    "all resolved atom coordinates finite:",
    bool(torch.isfinite(pose_stack.coords[pose_stack.real_atoms]).all()),
)
print("context reuses ParameterDatabase:", build_context.parameter_database is param_db)
field shape dtype
coords(1, 1231, 3)torch.float32
block_coord_offset(1, 76)torch.int32
block_type_ind(1, 76)torch.int32
chain_id(1, 76)torch.int32
real_atoms(1, 1231)torch.bool
n_poses=1, max_n_blocks=76, max_n_pose_atoms=1231
first five block types: ['MET:nterm', 'GLN', 'ILE', 'PHE', 'VAL']
PackedBlockTypes device: cpu
all resolved atom coordinates finite: True
context reuses ParameterDatabase: True

Expected observations. coords has shape [n_poses, max_n_pose_atoms, 3]; block-indexed fields have shape [n_poses, max_n_blocks, ...]. Padding is represented by sentinel block indices, while real_atoms identifies coordinate rows belonging to actual atoms. During construction TMol evaluates compatible terminal block-type alternatives and selects the best match. Unselected alternatives are normal internal candidates rather than malformed atoms, although the selector may emit a diagnostic warning while still constructing the correct terminal variant; this notebook captures those diagnostics separately. The finite-coordinate check is only a construction sanity check; the round-trip section performs the quantitative structural validation.

pdb_info preserves author-facing labels separately from the integer chain and block indices used by kernels. The block atom counts below describe the selected TMol residue types, which may include built atoms absent from the deposited structure.

[4]:
residue_frame = pd.DataFrame(
    {
        "block_index": np.arange(pose_stack.max_n_blocks)[real_blocks.cpu().numpy()],
        "chain": pose_stack.pdb_info.chain_labels[0, real_blocks.cpu().numpy()],
        "residue_number": pose_stack.pdb_info.residue_labels[
            0, real_blocks.cpu().numpy()
        ],
        "block_type": block_names,
        "n_atoms": pose_stack.n_ats_per_block[0, real_blocks].detach().cpu().numpy(),
    }
)
show_table(residue_frame)
block_index chain residue_number block_type n_atoms
0A1MET:nterm19
1A2GLN17
2A3ILE19
3A4PHE20
4A5VAL16
5A6LYS22
6A7THR14
7A8LEU19
8A9THR14
9A10GLY7
(66 more rows not shown)

Scientific round-trip and PDB compatibility export#

biotite_from_pose_stack() is the direct scientific inverse of the import path: it returns a Biotite structure from TMol’s blocks and coordinates without first serializing through PDB. Supplying the build context’s canonical ordering is important when custom residue or ligand types are present. The result is a TMol-built AtomArray, not a copy of the deposited atom_array: it reflects the selected chemical model.

The next cell reports net atom-count changes without claiming unavailable atom-level provenance. It then compares coordinates by the practical author label (chain, residue number, insertion code, atom name). For common heavy-atom labels it reports an unaligned RMSD and maximum displacement in the original coordinate frame, plus the largest-displacement labels. The clean 1UBQ fixture has fixture-specific assertions on those quantitative values.

The built structure is also converted back to a PoseStack with context=build_context. This reuses the parameter database, canonical ordering, residue-type set, and device-packed block types; only structure-dependent canonicalization and pose construction are repeated.

PDB remains useful for compatibility, so the cell also writes and reads a PDB file and quantifies the coordinate rounding on common heavy atoms. Its path is visible for download. PDB cannot carry all mmCIF metadata or reliable ligand bond orders, so compare intended chemistry rather than treating that file export as lossless.

[5]:
tmol_atom_array = biotite_from_pose_stack(
    pose_stack, build_context.canonical_ordering
)
assert isinstance(tmol_atom_array, AtomArray)


def atom_indices_by_author_label(structure):
    """Map practical author labels to indices; labels are not provenance."""
    labels = [
        (
            str(structure.chain_id[i]),
            int(structure.res_id[i]),
            str(structure.ins_code[i]).strip(),
            str(structure.atom_name[i]).strip(),
        )
        for i in range(structure.array_length())
    ]
    duplicates = [label for label, count in Counter(labels).items() if count > 1]
    if duplicates:
        raise ValueError(f"author atom labels are not unique: {duplicates[:3]}")
    return dict(zip(labels, range(len(labels))))


def common_heavy_atom_comparison(reference, comparison):
    reference_indices = atom_indices_by_author_label(reference)
    comparison_indices = atom_indices_by_author_label(comparison)
    common_labels = sorted(reference_indices.keys() & comparison_indices.keys())
    heavy_labels = [
        label
        for label in common_labels
        if reference.element[reference_indices[label]].upper() != "H"
        and comparison.element[comparison_indices[label]].upper() != "H"
    ]
    if not heavy_labels:
        raise ValueError("no common heavy-atom author labels")

    reference_xyz = np.array(
        [reference.coord[reference_indices[label]] for label in heavy_labels]
    )
    comparison_xyz = np.array(
        [comparison.coord[comparison_indices[label]] for label in heavy_labels]
    )
    displacements = np.linalg.norm(comparison_xyz - reference_xyz, axis=1)
    displacement_frame = pd.DataFrame(
        {
            "author_atom_label": [
                f"{chain}/{resid}{ins_code}/{atom_name}"
                for chain, resid, ins_code, atom_name in heavy_labels
            ],
            "displacement_A": displacements,
        }
    ).sort_values("displacement_A", ascending=False)
    metrics = {
        "common_heavy_atom_labels": len(heavy_labels),
        "reference_only_atom_labels": len(reference_indices.keys() - comparison_indices.keys()),
        "comparison_only_atom_labels": len(comparison_indices.keys() - reference_indices.keys()),
        "heavy_atom_RMSD_A": float(np.sqrt(np.mean(displacements**2))),
        "max_heavy_atom_displacement_A": float(displacements.max()),
    }
    return metrics, displacement_frame


deposited_indices = atom_indices_by_author_label(atom_array)
built_indices = atom_indices_by_author_label(tmol_atom_array)
deposited_only_labels = deposited_indices.keys() - built_indices.keys()
built_only_labels = built_indices.keys() - deposited_indices.keys()
known_residue_names = set(build_context.canonical_ordering.restype_io_equiv_classes)

deposited_only_reasons = Counter()
for label in deposited_only_labels:
    atom_index = deposited_indices[label]
    residue_name = str(atom_array.res_name[atom_index])
    atom_name = str(atom_array.atom_name[atom_index]).strip()
    if residue_name == "HOH":
        reason = "water is excluded by the current conversion path"
    elif residue_name not in known_residue_names:
        reason = "residue name is not recognized by the canonical ordering"
    elif atom_name not in build_context.canonical_ordering.restypes_atom_index_mapping.get(
        residue_name, {}
    ):
        reason = "atom name is absent from the residue's canonical mapping"
    else:
        reason = "label absent after block/variant selection; no finer public reason is exposed"
    deposited_only_reasons[reason] += 1

common_label_count = len(deposited_indices.keys() & built_indices.keys())
audit_rows = [
    {
        "category": "deposited model 1",
        "atoms": atom_array.array_length(),
        "interpretation": "atoms read from the checked-in mmCIF model",
    },
    {
        "category": "common author labels",
        "atoms": common_label_count,
        "interpretation": "same chain/residue/insertion-code/atom-name; not provenance",
    },
]
audit_rows.extend(
    {
        "category": "deposited-only labels",
        "atoms": count,
        "interpretation": reason,
    }
    for reason, count in deposited_only_reasons.items()
)
audit_rows.append(
    {
        "category": "TMol-built-only labels",
        "atoms": len(built_only_labels),
        "interpretation": (
            "labels present only after chemical-model selection/building; "
            "exact per-atom construction provenance is not exposed"
        ),
    }
)
audit_rows.append(
    {
        "category": "TMol-built total",
        "atoms": tmol_atom_array.array_length(),
        "interpretation": "atoms exported from the selected TMol block types",
    }
)
show_table(pd.DataFrame(audit_rows))

deposited_metrics, deposited_displacements = common_heavy_atom_comparison(
    atom_array, tmol_atom_array
)
assert deposited_metrics["heavy_atom_RMSD_A"] < 0.05
assert deposited_metrics["max_heavy_atom_displacement_A"] < 0.10

reuse_diagnostics = StringIO()
try:
    with redirect_stdout(reuse_diagnostics), redirect_stderr(reuse_diagnostics):
        reused_pose_stack = pose_stack_from_biotite(
            tmol_atom_array,
            torch_device=device,
            context=build_context,
            no_optH=True,
        )
except Exception:
    print(reuse_diagnostics.getvalue())
    raise
reused_atom_array = biotite_from_pose_stack(
    reused_pose_stack, build_context.canonical_ordering
)
reuse_metrics, reuse_displacements = common_heavy_atom_comparison(
    tmol_atom_array, reused_atom_array
)
assert reuse_metrics["heavy_atom_RMSD_A"] < 0.01
assert reuse_metrics["max_heavy_atom_displacement_A"] < 0.02

roundtrip_path = Path(tempfile.gettempdir()) / "1ubq_tmol_roundtrip.pdb"
# PDB cannot encode every TMol/Biotite annotation; that limitation is already
# explained above, so suppress Biotite's duplicate compatibility warning.
with warnings.catch_warnings():
    warnings.simplefilter("ignore", UserWarning)
    write_pose_stack_pdb(pose_stack, str(roundtrip_path))
    roundtrip_array = PDBFile.read(str(roundtrip_path)).get_structure(
        model=1,
        include_bonds=True,
        extra_fields=["occupancy", "b_factor"],
    )
pdb_metrics, pdb_displacements = common_heavy_atom_comparison(
    tmol_atom_array, roundtrip_array
)
assert pdb_metrics["heavy_atom_RMSD_A"] < 0.01
assert pdb_metrics["max_heavy_atom_displacement_A"] < 0.02

comparison_frame = pd.DataFrame(
    [
        {"comparison": "deposited model 1 → TMol-built", **deposited_metrics},
        {"comparison": "TMol-built → context-reused TMol-built", **reuse_metrics},
        {"comparison": "TMol-built → PDB compatibility read", **pdb_metrics},
    ]
)
show_table(comparison_frame)
print("largest deposited-to-built common-heavy-atom displacements:")
show_table(deposited_displacements.head(5))
print(
    "context PackedBlockTypes reused:",
    reused_pose_stack.packed_block_types is build_context.packed_block_types,
)
print("wrote:", roundtrip_path.resolve())
category atoms interpretation
deposited model 1660atoms read from the checked-in mmCIF model
common author labels602same chain/residue/insertion-code/atom-name; not provenance
deposited-only labels58water is excluded by the current conversion path
TMol-built-only labels629labels present only after chemical-model selection/building; exact per-atom construction provenance is not exposed
TMol-built total1231atoms exported from the selected TMol block types
comparison common_heavy_atom_labels reference_only_atom_labels comparison_only_atom_labels heavy_atom_RMSD_A max_heavy_atom_displacement_A
deposited model 1 → TMol-built602586290.00.0
TMol-built → context-reused TMol-built602000.00.0
TMol-built → PDB compatibility read602000.00.0
largest deposited-to-built common-heavy-atom displacements:
author_atom_label displacement_A
A/1/C0.0
A/1/CA0.0
A/1/CB0.0
A/1/CE0.0
A/1/CG0.0
context PackedBlockTypes reused: True
wrote: /tmp/1ubq_tmol_roundtrip.pdb

Direct PDB API, residue slices, and batched output#

tmol.pose_stack_from_pdb() is the concise compatibility path for a PDB filename or PDB lines. residue_start and residue_end select a zero-based, half-open range in parsed residue order; they are not PDB author residue numbers. A slice is normally treated as a new chain segment with termini. To represent an internal unresolved cut instead, pass res_not_connected[p, i, 0] = True at a missing upstream connection or [..., 1] = True at a missing downstream connection. At a selected range boundary, those flags preserve a nonterminal block with an incomplete connection rather than inventing terminal chemistry.

write_pose_stack_pdb() writes every pose in one PoseStack as a PDB MODEL. Split the batch first when downstream software requires one file per model. Both exports inherit PDB’s metadata and ligand-chemistry limitations.

OpenFold-style prediction tensors#

For canonical proteins, tmol.pose_stack_from_openfold(result) consumes aatype with shape [batch, residues], final positions with shape [batch, residues, atom14, 3] (stored by OpenFold under the final recycle), and chain_index with shape [batch, residues]. Missing supported atoms, including hydrogens, are built differentiably: if the input position tensor requires gradients, a TMol score can backpropagate to the supplied prediction coordinates. Combine this adapter with the per-residue-root forest in Tutorial 05 for NN-like frames.

See the Task index and structure I/O and integrations for the stable prediction-adapter entry points.

[6]:
pdb_path = repo_root / "tmol/tests/data/pdb/1ubq.pdb"
full_pdb_pose = tmol.pose_stack_from_pdb(str(pdb_path), device=device)

# Parsed residue positions 19:25 correspond to six residues in this fixture.
# Mark both outer connections incomplete so this is an internal fragment, not
# newly capped N/C termini.
internal_cut_flags = torch.zeros((1, 6, 2), dtype=torch.bool, device=device)
internal_cut_flags[0, 0, 0] = True
internal_cut_flags[0, -1, 1] = True
internal_slice = tmol.pose_stack_from_pdb(
    str(pdb_path),
    device=device,
    residue_start=19,
    residue_end=25,
    res_not_connected=internal_cut_flags,
)
assert internal_slice.max_n_blocks == 6
assert int(internal_slice.inter_residue_connections[0, 0, 0, 0]) == -1
assert int(internal_slice.inter_residue_connections[0, -1, 1, 0]) == -1

pdb_batch = PoseStackBuilder.from_poses([full_pdb_pose] * 3, device=device)
with tempfile.TemporaryDirectory(prefix="tmol-pdb-output-") as temp_dir:
    temp_dir = Path(temp_dir)
    multi_model_path = temp_dir / "ubiquitin_batch.pdb"
    write_pose_stack_pdb(pdb_batch, str(multi_model_path))
    model_count = sum(
        line.startswith("MODEL ") for line in multi_model_path.read_text().splitlines()
    )
    separate_paths = []
    for pose_index in range(pdb_batch.n_poses):
        output_path = temp_dir / f"ubiquitin_{pose_index:02d}.pdb"
        write_pose_stack_pdb(pdb_batch.split(pose_index), str(output_path))
        separate_paths.append(output_path)
    assert model_count == pdb_batch.n_poses
    assert all(path.is_file() and path.stat().st_size > 0 for path in separate_paths)

print(
    f"direct PDB blocks={full_pdb_pose.max_n_blocks}; "
    f"internal slice blocks={internal_slice.max_n_blocks}"
)
print(
    f"batch poses={pdb_batch.n_poses}; multi-model MODEL records={model_count}; "
    f"separate files={len(separate_paths)}"
)
direct PDB blocks=76; internal slice blocks=6
batch poses=3; multi-model MODEL records=3; separate files=3

Select and visualize deposited atoms#

Biotite stores chain, residue, atom-name, and element annotations as NumPy arrays. Compose Boolean masks directly so the selection is explicit, dependency-free, and easy to test. Here the masks describe atom_array, the deposited experimental model; do not apply them to the differently sized tmol_atom_array built by TMol.

Pass pose_stack or tmol_atom_array to the viewer instead when the scientific question concerns built atoms or resolved chemistry, and recompute highlights in that object’s indexing. tmol.selection_gallery() accepts an AtomArray plus named Boolean masks; click a button to restyle and center the same model.

[7]:
selection_mask = (
    (atom_array.chain_id == "A")
    & (atom_array.res_id >= 1)
    & (atom_array.res_id <= 10)
    & np.isin(atom_array.atom_name, ["N", "CA", "C", "O"])
)
sidechain_mask = (
    (atom_array.chain_id == "A")
    & (atom_array.res_id >= 1)
    & (atom_array.res_id <= 10)
    & ~np.isin(atom_array.atom_name, ["N", "CA", "C", "O", "OXT"])
)
selected_atoms = atom_array[selection_mask]
print("selected backbone atoms:", selected_atoms.array_length())
print("selected residues:", np.unique(selected_atoms.res_id))
try:
    display(
        tmol.selection_gallery(
            atom_array,
            {
                "Backbone, residues 1–10": selection_mask,
                "Side chains, residues 1–10": sidechain_mask,
                "All Cα atoms": atom_array.atom_name == "CA",
            },
            width=720,
            height=420,
        )
    )
except ImportError as exc:
    print("Selection viewer unavailable in this environment:", exc)
selected backbone atoms: 40
selected residues: [ 1  2  3  4  5  6  7  8  9 10]
pick a selection · drag to rotate · scroll to zoom · click a highlighted atom to label it

Rosetta comparison#

A Rosetta Pose is a single rich object with residues, conformation, energies, and attached metadata. A TMol PoseStack is deliberately batch-first: chemistry/connectivity metadata is block-indexed, while Cartesian coordinates are contiguous PyTorch tensors. One TMol block is closest to a residue-like chemical unit, but not every block must be a canonical amino acid.

Rosetta applications often obtain behavior through a process-wide flags/options system. TMol has no global Rosetta-style flags layer: device, parameter database, score-function options, I/O choices, and protocol settings are explicit Python arguments or object configuration. This is more verbose but makes notebook state and batching assumptions visible.

Keep the Rosetta-to-TMol crosswalk open when translating a workflow; it separates genuine API parallels from protocol layers TMol does not implement. See the official full tutorials for Working with Rosetta, input and output, core concepts, commonly used options, and PyRosetta Pose basics.

Next: batch scoring#

This notebook followed one structure from deposited atoms to a TMol-built PoseStack and back. GPU Batching with TMol next keeps those I/O and device choices explicit while assembling many compatible poses for one scoring call; Scoring and Analysis then interprets the score terms.

Exercises#

  1. Change the device selection to force CPU and confirm all tensor devices agree.

  2. Select residues 20–30 and heavy side-chain atoms with a Biotite/NumPy Boolean mask; assert the selected author labels.

  3. Build the same PDB residue range once as a new terminal fragment and once with internal-cut flags; compare selected block-type names and connectivity.

  4. Reuse build_context to import the PDB-read roundtrip_array; compare its block types with the direct reused_pose_stack and explain any differences.

  5. Write a two-pose batch as both one multi-model PDB and separate files; verify model count and author residue labels rather than raw text equality.

References#


Download this notebook