Molecular graphs, equivariance and docking
Nonbacktracking directed-bond recurrence and atom readout
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Propagate messages along directed bonds while excluding the immediate reverse edge. Return each recurrent bond state, aggregate atom messages, and the final atom readout.
Function signature
nonbacktracking_bond_messages(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
depth: int = 3,
undirected: bool = False,
dropout_p: float = 0.0,
) -> dict[str, torch.Tensor]Worked example
Trace a three-atom chain
Directed edges are 0→1, 1→0, 1→2, and 2→1, each initialized to 1. For 1→2, incoming messages at atom 1 sum to 2. Subtract its reverse edge 2→1, then add the original projection: 2-1+1=2. Updated edge states are [1, 2, 2, 1]. Final atom sums are [2, 2, 2]; adding each atom feature 1 gives atom outputs [3, 3, 3].
import torch
activations = {
'atom_features': torch.tensor([[1.0], [1.0], [1.0]], dtype=torch.float64, requires_grad=True),
'edge_features': torch.tensor([[0.0], [0.0], [0.0], [0.0]], dtype=torch.float64, requires_grad=True),
}
parameters = {
'w_i': torch.tensor([[1.0], [0.0]], dtype=torch.float64, requires_grad=True),
'b_i': torch.tensor([0.0], dtype=torch.float64, requires_grad=True),
'w_h': torch.tensor([[1.0]], dtype=torch.float64, requires_grad=True),
'b_h': torch.tensor([0.0], dtype=torch.float64, requires_grad=True),
'w_o': torch.tensor([[1.0], [1.0]], dtype=torch.float64, requires_grad=True),
'b_o': torch.tensor([0.0], dtype=torch.float64, requires_grad=True),
}
masks = {
'atom_mask': torch.tensor([True, True, True], dtype=torch.bool),
'edge_mask': torch.tensor([True, True, True, True], dtype=torch.bool),
'message_keep': torch.tensor([[[True], [True], [True], [True]]], dtype=torch.bool),
'atom_keep': torch.tensor([[True], [True], [True]], dtype=torch.bool),
}
structure = {
'edge_index': torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]], dtype=torch.int64),
'reverse_index': torch.tensor([1, 0, 3, 2], dtype=torch.int64),
}
depth = 2
undirected = False
dropout_p = 0.0
result = nonbacktracking_bond_messages(activations=activations, parameters=parameters, masks=masks, structure=structure, depth=depth, undirected=undirected, dropout_p=dropout_p)Expected return
{
'initial_projection': torch.tensor([[1.0], [1.0], [1.0], [1.0]], dtype=torch.float64),
'bond_states': torch.tensor([[[1.0], [1.0], [1.0], [1.0]], [[1.0], [2.0], [2.0], [1.0]]], dtype=torch.float64),
'atom_messages': torch.tensor([[2.0], [2.0], [2.0]], dtype=torch.float64),
'atom_hidden': torch.tensor([[3.0], [3.0], [3.0]], 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. |
depthintDefault 3 | Number of stored bond states, including the initial projection. Perform exactly depth-1 recurrent updates. |
undirectedboolDefault False | Average reverse-paired messages before each update when True. |
dropout_pfloatDefault 0.0 | Dropout probability associated with supplied message/readout keep masks. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Implement the directed-bond recurrence and bond-to-atom readout; do not replace it with a generic node MLP. Let
X=\mathrm{atom\_features}[N,A],E=\mathrm{edge\_features}[D,B], withA,B,H\ge 1andN,Dpossibly zero. Floating tensors share a finite CPU\mathrm{float32}/\mathrm{float64}dtype. edge_index[2,D]int64stores sources(e)then destinationt(e).reverse_index[D]int64maps each active edge to its unique paired reverser(e):r(r(e))=e,r(e)\ne e,s(r(e))=t(e),t(r(e))=s(e). Active edges have distinct in-range endpoints whoseatom_maskisTrue, and their reverse is active.- Parallel edge pairs are allowed; exclude the paired identity, not every edge
with the same endpoints.
edge_mask[D]andatom_mask[N]are bool,Truemeans valid. Inactive edge and reverse-index payloads may be arbitrary integers and must never be indexed. - Invalid graph invariants
other than the documented configuration checks are outside the contract. Features at masked rows are ignored. All projections use row vectors:
w_i[A+B,H],b_i[H],w_h[H,H],b_h[H],w_o[A+H,H],b_o[H]. - Define
\rho(z)=\operatorname{max}(z,0)elementwise with derivative zero atz=0. - For valid edges:
\begin{aligned}Z_e&=\operatorname{concat}(X_{s(e)},E_e)\mathbin{@}w_i+b_i,\\H_e^0&=\rho(Z_e).\end{aligned}Initialization is not dropped out. PreserveZ, the preactivation projection, as the residual at EVERY update.depthdefaults to3and must be an integer from1through8. - Execute exactly
\mathrm{depth}-1updates.undirecteddefaults toFalse. At each updatet=1,...,\mathrm{depth}-1letG=H^{t-1}in directed mode; in the supported undirected option, first useG_{e}=(H_{e}^{t-1}+H_{r(e)}^{t-1})/2. This does not averageZand does not overwrite the previously returned state. - At each update, compute
M_e=\sum_{f\ active:\,t(f)=s(e),\ f\ne r(e)}G_f, thenH_e^t=ReLU(Z_e+M_e w_h+b_h)\odot message\_keep[t-1,e]/(1-p). The supplied keep mask has shape (depth−1,D,H); p=dropout_p lies in [0,1) and defaults to zero. - The deterministic mask-and-scale operation is always applied; for ordinary no-dropout
evaluation use
p=0and all-Truemasks. Masks are treated as given discrete outcomes, never sampled. After the last stateH^{\mathrm{depth}-1}, sum all incoming active directed bonds (no reverse subtraction here): \begin{aligned}A_v&=\sum_{\substack{e\ \mathrm{active}\\t(e)=v}}H_e^{\mathrm{depth}-1},\\Y_v&=\rho\bigl(\operatorname{concat}(X_v,A_v)\mathbin{@}w_o+b_o\bigr)\odot\frac{\mathrm{atom\_keep}[v]}{1-p}.\end{aligned}Hereatom_keep[N,H]is also supplied Boolean.- Return
\mathrm{initial\_projection}=Z[D,H],bond_states[\mathrm{depth},D,H]containingH^{0}throughH^{\mathrm{depth}-1},\mathrm{atom\_messages}=A[N,H], and\mathrm{atom\_hidden}=Y[N,H]. MaskZand every bond state to exactly zero for inactive edges, and atom outputs to zero for padded atoms. - An isolated valid atom has
A_{v}=0but may have a nonzero descriptor/bias readout. Empty axes are retained, not replaced by dummy nodes. All outputs remain connected with zero derivatives to unused parameters, includingw_h/b_hat\mathrm{depth}=1and edge-only parameters whenD=0. Reject invalid depth/dropout_pwithValueError. - Optional additional atom-descriptor projections, molecular pooling, task prediction heads and training are outside this core; no descriptors, weights, random variates or chemical structures are inferred or loaded.
Why this matters
Incoming messages summarize the source atom’s neighborhood; subtracting the uniquely paired reverse edge removes the forbidden immediate return path.
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 nonbacktracking_bond_messages(
activations: dict,
parameters: dict,
masks: dict,
structure: dict,
depth: int = 3,
undirected: bool = False,
dropout_p: float = 0.0,
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
initial_projection: (D, H).
bond_states: (depth, D, H).
atom_messages: (N, H).
atom_hidden: (N, H)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement nonbacktracking_bond_messages")
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.