Molecular graphs, equivariance and docking
Radial-weighted irrep convolution with receiver aggregation
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Combine node irreps with edge harmonics using learned radial path weights. Aggregate directed messages into receiver nodes under the selected degree-normalization rule.
Function signature
equivariant_message_passing(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
normalization: str = 'sender_sqrt',
radial_activation: str = 'silu',
symmetry: str = 'o3',
) -> dict[str, torch.Tensor]Worked example
A vector message uses sender normalization and a radial weight
Two valid nodes have polar vectors [2,4,0] and [1,0,0]; the single valid edge is receiver 1, sender 0. With identity input/output channel weights and sender_sqrt normalization, sender 0's avg_neighbors=4 divides its vector by \sqrt4, giving [1,2,0].
One legal vector-times-even-scalar path uses harmonic 1 and a CG table that preserves each vector component. The radial input is 2: first-layer weight 1, bias 0, and ReLU give 2; second weight 3 and bias 1 give radial weight 7. One path/channel and path scale 1 add no normalization factor. Edge weight 0.5 produces edge_messages=[3.5,7,0]. Only receiver 1 receives it: node outputs are [[0,0,0],[3.5,7,0]] and degrees [0,1].
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. |
normalizationstrDefault 'sender_sqrt' | Choose "sender_sqrt", "sum", "mean". Config normalization='sender_sqrt' default divides h [n] by \sqrt{\mathrm{avg\_neighbors}[n]} before gathering the sender; this is the audited NequIP placement. |
radial_activationstrDefault 'silu' | Choose SiLU or ReLU for the hidden radial-weight projection. |
symmetrystrDefault 'o3' | "o3" requires both angular-momentum triangle bounds and matching output parity; "so3" enforces the triangle bounds without parity filtering. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Clean node component padding and node masks, then
h[n,t,o,m]=\sum_{c} \mathrm{features}[n,t,c,m]\cdot w_{\mathrm{in}}[t,c,o]. Confignormalization='sender_sqrt'default dividesh [n]by\sqrt{\mathrm{avg\_neighbors}[n]}before gathering the sender; this is the audited NequIP placement. - Alternatives 'sum' and 'mean' do not scale
h; mean divides each receiver's final sum by its accepted incoming-edge count clamped to at least one. An edge is accepted only ifedge_validand both receiver/sender are valid.Falseedge_validindices may be sentinel values. - Compute
radialweightsW=\mathrm{activation}(\mathrm{radial}\mathbin{@}\mathrm{radial\_w1}+\mathrm{radial\_b1})\mathbin{@}\mathrm{radial\_w2}+\mathrm{radial\_b2}, reshaped[E,P,\mathrm{Ci},\mathrm{Co}];radial_activation='silu'default or 'relu'. - For each legal CG path (input type, edge harmonic type, output type), couple
h [\mathrm{sender}]andedge_shwith its fixed CG tensor, then channel-mix byW. - Multiply each path by
\mathrm{path\_scale}/\sqrt{\mathrm{Ci}\cdot \mathrm{active\_paths\_to\_output\_type}}, sumpathsby output type, apply type-wisew_outon channels, multiplyedge_weight, and sum into receiver. This is algebraically the same as applyingw_outafter aggregation. symmetry='o3'default enforces triangle bounds and\mathrm{parity\_out}=\mathrm{parity\_in}\cdot \mathrm{parity\_edge}; 'so3' omits parity filtering. Return also final per-edge messages, accepteddegree, andWzeroed for rejected edges/illegalpaths. No tensor-component bias is allowed.- All false-edge payloads, invalid node data/
avg_neighborsand padded components may be nonfinite and must be ignored.E=0is supported;N\ge 1. A packed block(l,p)has2\cdot l+1real components and multiplicityC;pis+1or-1under inversion.Dis a common padded width. - Padding is ignored, including
NaNpadding. Distinct copies of a type are channels, not XYZ coordinates. The fixture basis is Cartesianl=1 [x,y,z]andl=2orthonormal symmetric-traceless tensors: Q=[(\mathrm{xz}+\mathrm{zx})/\sqrt{2},(\mathrm{xy}+\mathrm{yx})/\sqrt{2},\mathrm{diag}(-1,2,-1)/\sqrt{6},(\mathrm{yz}+\mathrm{zy})/\sqrt{2},\mathrm{diag}(-1,0,1)/\sqrt{2}].Fixed row-orthonormal CG tensors intertwine these bases; phases are defined by the supplied tables, not inferred from an external checkpoint.- Supplied higher-
degreetables must obey the same stated normalization and representation law.
Why this matters
Distances create invariant learned weights; spherical harmonics and Clebsch–Gordan contractions carry the geometric transformation rules.
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 equivariant_message_passing(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
normalization: str = 'sender_sqrt',
radial_activation: str = 'silu',
symmetry: str = 'o3',
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
features: (N, O, Co, D).
edge_messages: (E, O, Co, D).
radial_weights: (E, P, Ci, Co).
degree: (N,)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement equivariant_message_passing")
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.
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.