MSA and pair representations
Noisy Atom Pair Features
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Build reference-space conditioning for each query atom and supplied key slot, then separately add the projected noisy coordinates to the returned atom features. The noisy branch changes atom_features; query_condition, key_condition, and pair features retain their reference-conditioning values. Return pair vectors before and after the residual MLP with these diagnostics.
Function signature
af3_noisy_atom_pair_features(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
) -> dict[str, torch.Tensor]Worked example
Worked example
Reference-space pair features are [[6, 6], [7, 9]] before the residual MLP. Three half-scale projections add one eighth of each positive pair feature. Noisy positions change atom_features to [1.5, 3], while query_condition remains [1, 2].
import torch
activations = {
'x': torch.tensor([[[1.0], [2.0]]], dtype=torch.float64, requires_grad=True),
}
parameters = {
'row_weight': torch.tensor([[1.0]], dtype=torch.float64, requires_grad=True),
'col_weight': torch.tensor([[2.0]], dtype=torch.float64, requires_grad=True),
'offset_weight': torch.tensor([[1.0], [0.0], [0.0]], dtype=torch.float64, requires_grad=True),
'distance_weight': torch.tensor([[2.0]], dtype=torch.float64, requires_grad=True),
'valid_weight': torch.tensor([[1.0]], dtype=torch.float64, requires_grad=True),
'pair_mlp_weight': torch.tensor([[[0.5]], [[0.5]], [[0.5]]], dtype=torch.float64, requires_grad=True),
'noisy_weight': torch.tensor([[0.5], [0.0], [0.0]], dtype=torch.float64, requires_grad=True),
}
masks = {
'mask': torch.tensor([[True, True]], dtype=torch.bool),
'window_mask': torch.tensor([[[True, True], [True, True]]], dtype=torch.bool),
}
structure = {
'ref_positions': torch.tensor([[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]], dtype=torch.float64, requires_grad=True),
'ref_space_uid': torch.tensor([[0, 0]], dtype=torch.int64),
'key_index': torch.tensor([[[0, 1], [0, 1]]], dtype=torch.int64),
'noisy_positions': torch.tensor([[[1.0, 0.0, 0.0], [2.0, 0.0, 0.0]]], dtype=torch.float64, requires_grad=True),
}
result = af3_noisy_atom_pair_features(activations=activations, parameters=parameters, masks=masks, structure=structure)Expected return
{
'query_condition': torch.tensor([[[1.0], [2.0]]], dtype=torch.float64),
'key_condition': torch.tensor([[[[1.0], [2.0]], [[1.0], [2.0]]]], dtype=torch.float64),
'atom_features': torch.tensor([[[1.5], [3.0]]], dtype=torch.float64),
'offset_mask': torch.tensor([[[True, True], [True, True]]], dtype=torch.bool),
'pair_features': torch.tensor([[[[6.0], [6.0]], [[7.0], [9.0]]]], dtype=torch.float64),
'output': torch.tensor([[[[6.75], [6.75]], [[7.875], [10.125]]]], dtype=torch.float64),
}Parameters
| Parameter | Shape and meaning |
|---|---|
activationsdict | Supplied intermediate tensors. |
parametersdict | Supplied model parameters, keyed as shown below. |
masksdict | Supplied masks. Each field below defines what True includes or permits. |
structuredict | Fields supplied together in this dictionary. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- x is
[B,A,C]; mask marks valid atoms. key_index[B,A,K]chooses atom slots per query, and window_mask marks allowed slots. A slot requires valid query/key atoms, an in-range index, and window_mask=True; repeated keys stay repeated. - Begin with cleaned x as query_condition.
- Gather key_condition at each slot. Initial pair features are ReLU(query_condition) @ row_weight plus ReLU(key_condition) @ col_weight.
- Offsets point from key to query:
r_i-r_j. Add offset @ offset_weight plus1/(1+\|offset\|^2)@ distance_weight only when the allowed atoms share ref_space_uid. Also add offset_mask (as a scalar feature) @ valid_weight. - Pass pair_features through three consecutive ReLU-then-matrix-product stages using pair_mlp_weight[0], [1], [2], then add this result to pair_features as a residual. No stage has a bias.
- Return zero key_condition/pair_features/output for disallowed slots, zero atom_features for invalid atoms, and boolean offset_mask. query_condition is the cleaned atom condition.
- atom_features adds noisy_positions @ noisy_weight to query_condition after cleaning invalid noisy coordinates. This addition does not change query_condition, key_condition, or pair features.
- Use CPU float32/float64 tensors with one shared floating dtype/device. Preserve strided layouts and do not mutate supplied tensors.
- Use raw tensor operations, not a ready-made implementation of the requested primitive. Supplied parameters and random variates must not be reinitialized.
Why this matters
AF3 combines atom conditions with geometry only when reference coordinates share a common space. Noisy coordinates update atom features separately, so pair conditioning stays tied to the reference conformer.
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 af3_noisy_atom_pair_features(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
pair_features: (B,A,K,P).
output: (B,A,K,P).
query_condition: (B, A, C).
key_condition: (B, A, K, C).
offset_mask: (B, A, K).
atom_features: (B, A, C)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement af3_noisy_atom_pair_features")
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.