Geometry and structural losses · INTEGRATION
Peptide geometry, steric violations and polymer clashes
AlphaFold2OpenFoldAlphaFold3
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Measure structural inconsistencies using the selected rule. Peptide mode penalizes bond-length and angle deviations; within-residue mode penalizes distances outside supplied bounds; between-residue mode penalizes excessive radius overlap. AF3 clash mode instead counts polymer atoms with an eligible close partner. Return only the chosen mode’s losses or counts and its atom/residue flags.
Function signature
structural_violation_loss(
activations: dict,
masks: dict,
structure: dict,
options: dict,
) -> dict[str, torch.Tensor]Worked example
Count clashing atoms at the inclusive radius
Atoms 0 and 2 are same-chain sequence neighbors, so their mutual distance is excluded. Both lie exactly one unit from atom 1 on another chain, so those cross-chain contacts count at radius 1. Nonpolymer atom 3 is ignored. Chain clash counts are [2,1,0] out of totals [3,1,0], giving fractions [2/3,1,0]. Chain 0 exceeds min_clashes=1; chain 1 exceeds min_fraction=0.75. The structure therefore has_clash=True.
import torch
activations = {
}
masks = {
'atom_mask': torch.tensor([[True, True, True, True, True]], dtype=torch.bool),
'polymer_mask': torch.tensor([[True, True, True, False, True]], dtype=torch.bool),
}
structure = {
'positions': torch.tensor([[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [8.0, 0.0, 0.0]]], dtype=torch.float64),
'residue_index': torch.tensor([[10, 10, 11, 13, 99]], dtype=torch.int64),
'chain_id': torch.tensor([[0, 1, 0, 0, 0]], dtype=torch.int64),
}
options = {
'clash_radius': 1.0,
'min_clashes': 1,
'num_chains': 3,
'min_fraction': 0.75,
'variant': 'af3_clash',
}
result = structural_violation_loss(activations=activations, masks=masks, structure=structure, options=options)Expected return
{
'atom_clash_mask': torch.tensor([[True, True, True, False, False]], dtype=torch.bool),
'chain_clash_counts': torch.tensor([[2, 1, 0]], dtype=torch.int64),
'chain_atom_counts': torch.tensor([[3, 1, 0]], dtype=torch.int64),
'chain_clash_fraction': torch.tensor([[0.6666666666666666, 1.0, 0.0]], dtype=torch.float64),
'has_clash': torch.tensor([True], dtype=torch.bool),
}Parameters
| Parameter | Shape and meaning |
|---|---|
activationsdict | Supplied intermediate tensors. |
masksdict | Supplied masks. Each field below defines what True includes or permits. |
structuredict | Fields supplied together in this dictionary. |
optionsdict | Scalar settings. Omitted keys use the defaults below. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Implement four explicitly different variants.
B, A,RandCare positive; inputs are CPU\mathrm{float32}/\mathrm{float64}(one common floating dtype),int64indices and bool masks.positions[B,A,3] are Angstrom coordinates;atom_mask[B,A] is true for present atoms. - Masked coordinates and ineligible geometric terms are ignored before squaring or normalization. All inputs are finite; eligible geometry/statistics must be within the chosen dtype's arithmetic range. Empty atom axes are outside the contract; all-masked batches, singleton atoms/residues, empty chain slots and noncontiguous tensors are supported.
variant='peptide'(default).backbone_atoms[B,R,3] indexespositionsinN, CA,Corder, each index in[0,A). Hereresidue_indexandchain_idhave shape [B,R];is_proline[B,R] is bool. A linkr\to r+1exists only if residue indices differ by\mathrm{exactly} +1AND chain IDs match.- For the
C_{r}--N_{r+1}length, require those two atoms; for\mathrm{CA\_r}--C_{r}--N_{r+1}, also requireCA_r; forC_{r}--N_{r+1}--\mathrm{CA}_{r+1}, also require the next residue's C-alpha atom,\mathrm{CA}_{r+1}. Never borrow a mask from an unrelated angle. Define\operatorname{norm_e}(v)=\sqrt{\sum(v^{2})+\mathrm{geometry\_eps}}. - The three metrics are
\operatorname{norm_e}(N_{\mathrm{next}}-C),\operatorname{dot}(\mathrm{CA}-C,N_{\mathrm{next}}-C)/(\operatorname{norm_e}(\mathrm{CA}-C)\cdot \operatorname{norm_e}(N_{\mathrm{next}}-C)), and\operatorname{dot}(C-N_{\mathrm{next}},\mathrm{CA\_next}-N_{\mathrm{next}})/(\operatorname{norm_e}(C-N_{\mathrm{next}})\cdot \operatorname{norm_e}(\mathrm{CA\_next}-N_{\mathrm{next}})). - Means/stds for the bond are
\mathrm{cn\_mean}[2]/\mathrm{cn\_std}[2], indexed by whether the NEXT residue is proline (0=\mathrm{other},1=\mathrm{proline}).\mathrm{angle\_mean}[2]/\mathrm{angle\_std}[2]contain the two cosine statistics in that order. - Standard deviations are positive; these are supplied scientific constants, not inferred from the predicted geometry.
- For metric
q, mean\muand stds,\mathrm{error}=\sqrt{(q-\mu)^{2}+\mathrm{error\_eps}}; soft\mathrm{loss}=\operatorname{max}(\mathrm{error}-\mathrm{soft\_tolerance}\cdot s,0), hard\mathrm{flag}=(\mathrm{error}>\mathrm{hard\_tolerance}\cdot s). Apply the term's mask to BOTH its soft loss and hard flag. link_losses[B,R -1,3] contains masked losses.mean_losses[B,3]divides each term's sum by its own valid-link\operatorname{count}+\mathrm{count\_eps}, including valid zero-loss links in that count.residue_loss[B,R] splits each link's total equally between its two residues.residue_violation[B,R] is true at either endpoint of any flagged term. Defaults\mathrm{geometry\_eps}=\mathrm{error\_eps}=\mathrm{count\_eps}=10^{-6};\mathrm{soft\_tolerance}=\mathrm{hard\_tolerance}=12.variant='within_residue'. Nowresidue_indexandchain_idare [B,A]; together they identify an atom's residue.lower_bound/upper_bound[B,A,A] give directed distance bounds, with0\le \mathrm{lower}\le \mathrm{upper}on eligible pairs.- An eligible directed entry
(i,j)has both atoms present,i\ne j, and equal residue AND chain IDs. Bounds need not be symmetric (chemical tables normally are).d_{\mathrm{ij}}=\sqrt{\sum((x_{i}-x_{j})^{2})+\mathrm{distance\_eps}}. pair_loss[B,A,A] is\operatorname{max}(\mathrm{lower}+\mathrm{tighten}-d,0)+\operatorname{max}(d-\mathrm{upper}+\mathrm{tighten},0)on eligible entries, otherwise zero. Hard violation uses the ORIGINAL bounds:d<\mathrm{lower}ORd>\mathrm{upper}, strictly; tightening never changes the hard threshold.variant='between_residue'.radii[B,A] are nonnegative van der Waalsradii.pair_exclusion[B,A,A] marks supplied covalent/peptide or disulfide exclusions; either orientation excludes the undirected pair.- Eligible entries have
i<j, both atoms present, different(\mathrm{chain},\mathrm{residue})identities, and neither exclusion entry set. Use the same\epsilondistance as variant2.\mathrm{pair\_loss}=\operatorname{max}(r_{i}+r_{j}-\mathrm{overlap\_soft}-d,0); hard violation isd<r_{i}+r_{j}-\mathrm{overlap\_hard}. - Lower-triangular, diagonal, same-residue and excluded entries are zero. The caller supplies chemical exclusions; do not guess bonds from distance alone.
- Variants
2/3returnpair_loss,\mathrm{mean\_loss}[B]=\sum(\mathrm{pair\_loss})/(number of eligible\mathrm{entries}+\mathrm{count\_eps}),per_atom_loss[B,A]=\mathrm{row\_sum}(\mathrm{pair\_loss})+\mathrm{column\_sum}(\mathrm{pair\_loss}), andatom_violation[B,A]=any incident hard violation. - Each eligible entry contributes its FULL loss to BOTH endpoints. Within-residue matrices retain both directions: for symmetric bounds an unordered pair contributes twice to each endpoint. Between-residue matrices keep one direction: an unordered pair contributes once to each endpoint.
- The within-residue mean is an added teaching diagnostic, not a separately claimed upstream loss. Defaults
\mathrm{distance\_eps}=10^{-10},\mathrm{count\_eps}=10^{-6},\mathrm{tighten}=0,\mathrm{overlap\_soft}=\mathrm{overlap\_hard}=1.5. Unused bounds/radiiare ignored. variant='af3_clash'.residue_index/chain_idare [B,A];polymer_mask[B,A] selects protein/RNA/DNA atoms. Onlyatom_maskANDpolymer_maskparticipates. A pair is eligible if its chain IDs differ OR its same-chain residue indices differ by more than1.- Test squared Euclidean
\mathrm{distance} \le \mathrm{clash\_radius}^{2}, with no\epsilon; the radius boundary is inclusive.atom_clash_mask[B,A] is true if an atom has at least one eligible neighbor, regardless of how many. \mathrm{num\_chains}=C(default1) supplies dense chain slots0..C-1; participating atoms must have an ID in that range.chain_clash_countsandchain_atom_counts[B,C] areint64;chain_clash_fraction[B,C]=counts/totals, or zero for an empty chain.has_clash[B]is true if ANY chain has\operatorname{count}>\mathrm{min\_clashes}OR\mathrm{fraction}>\mathrm{min\_fraction}; both inequalities are strict. Defaults\mathrm{clash\_radius}=1.1Angstrom,\mathrm{min\_clashes}=100,\mathrm{min\_fraction}=0.5. Non-polymer atoms do not affect numerator or denominator.- There is no bond/van-der-Waals loss, SASA calculation, or ranking arithmetic in this variant. All epsilons must be finite and strictly positive; other numerical tolerances must be finite and nonnegative.
min_fractionmust be in[0,1],min_clashesa nonnegative integer, andnum_chainsa positive integer. - Unknown variants and these invalid configurations raise
ValueError. No geometry/table shape validation is required outside the stated input domain. Variant-specific keys are required only for that variant. All other configuration keys are ignored. Loss variants return exact connected zeros when no term is eligible; bool outputs are false. - Each variant returns only its listed output subset.
Why this matters
Bad peptide geometry, within-residue distortions and overlaps are different problems. Soft variants provide penalties for optimization; the AF3 clash variant reports a hard structural flag. Select one variant before deciding what to count.
Starter code
You write the body of this function in the BioTorch workspace. Your code runs in an isolated PyTorch sandbox; Run checks the public examples and Submit grades the full test suite, including gradients.
import torch
def structural_violation_loss(
activations: dict,
masks: dict,
structure: dict,
options: dict,
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
link_losses: (B, R-1, 3). Returned only for peptide.
mean_losses: (B, 3). Returned only for peptide.
residue_loss: (B, R). Returned only for peptide.
residue_violation: (B, R). Returned only for peptide.
pair_loss: (B, A, A). Returned only for within_residue or between_residue.
mean_loss: (B,). Returned only for within_residue or between_residue.
per_atom_loss: (B, A). Returned only for within_residue or between_residue.
atom_violation: (B, A). Returned only for within_residue or between_residue.
atom_clash_mask: (B, A). Returned only for af3_clash.
chain_clash_counts: (B, C). Returned only for af3_clash.
chain_atom_counts: (B, C). Returned only for af3_clash.
chain_clash_fraction: (B, C). Returned only for af3_clash.
has_clash: (B,). Returned only for af3_clash."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement structural_violation_loss")
Ready to implement it?
3 progressive hints, a solution walkthrough, and grading that checks values, shapes and gradients are waiting in the workspace.
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Related exercises
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.