Molecular graphs, equivariance and docking
Directed molecular bonds and reverse-edge bookkeeping
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Expand each undirected bond slot into two directed edge slots. Return endpoints, reverse-edge indices, masks, and source-conditioned edge features, preserving isolated atoms.
Function signature
directed_bond_graph(
atom_features: torch.Tensor,
bond_features: torch.Tensor,
bonds: torch.Tensor,
atom_mask: torch.Tensor,
bond_mask: torch.Tensor,
) -> dict[str, torch.Tensor]Worked example
Chain and isolated atom
Bonds (0,1) and (1,2) become edges 0→1, 1→0, 1→2, 2→1. reverse_index is [1,0,3,2], and in_degree is [1,2,1,0]; atom 3 remains isolated. Each edge input concatenates its source atom descriptor with its original bond descriptor.
import torch
atom_features = torch.tensor([[0.6163984432976174, -0.11737156230437856], [-0.871515752829823, 0.22737251091226712], [-0.43380893696960837, -0.5594381581483507], [0.16133873905171972, 0.33521053319906396]], dtype=torch.float64, requires_grad=True)
bond_features = torch.tensor([[-0.2877030313877437, -0.16133740997286983], [-0.23865414504605093, 0.07281459402452221]], dtype=torch.float64, requires_grad=True)
bonds = torch.tensor([[0, 1], [1, 2]], dtype=torch.int64)
atom_mask = torch.tensor([True, True, True, True], dtype=torch.bool)
bond_mask = torch.tensor([True, True], dtype=torch.bool)
result = directed_bond_graph(atom_features=atom_features, bond_features=bond_features, bonds=bonds, atom_mask=atom_mask, bond_mask=bond_mask)Expected return
{
'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),
'edge_mask': torch.tensor([True, True, True, True], dtype=torch.bool),
'edge_features': torch.tensor([[-0.2877030313877437, -0.16133740997286983], [-0.2877030313877437, -0.16133740997286983], [-0.23865414504605093, 0.07281459402452221], [-0.23865414504605093, 0.07281459402452221]], dtype=torch.float64),
'edge_inputs': torch.tensor([[0.6163984432976174, -0.11737156230437856, -0.2877030313877437, -0.16133740997286983], [-0.871515752829823, 0.22737251091226712, -0.2877030313877437, -0.16133740997286983], [-0.871515752829823, 0.22737251091226712, -0.23865414504605093, 0.07281459402452221], [-0.43380893696960837, -0.5594381581483507, -0.23865414504605093, 0.07281459402452221]], dtype=torch.float64),
'in_degree': torch.tensor([1, 2, 1, 0], dtype=torch.int64),
}Parameters
| Parameter | Shape and meaning |
|---|---|
atom_featurestorch.Tensor | [N, A]Float X [N,A], A\ge 1: supplied finite atom descriptors; padded rows ignored. |
bond_featurestorch.Tensor | [M, B]Float F [M,B], B\ge 1: supplied finite descriptors for each undirected bond slot. |
bondstorch.Tensor | [M, 2]Int64 [M,2]: ordered endpoint pair for each bond; arbitrary integers allowed only when bond_mask=False. |
atom_masktorch.Tensor | [N]Bool [N]: True for an active atom. |
bond_masktorch.Tensor | [M]Bool [M]: True for a listed bond with distinct in-range endpoints; endpoint atom masks also apply. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Construct a fixed-capacity directed graph from prepared descriptors and an undirected bond list. Inputs are
atom_featuresX [N,A],bond_featuresF [M,B] (\mathrm{same} \mathrm{CPU} \mathrm{float32}/\mathrm{float64}, \mathrm{finite}),bonds[M,2]int64,atom_mask[N]bool, andbond_mask[M]bool. - A,
B \ge 1;N,Mmay be zero. Every bond withbond_mask[m]=Truemust list two distinct indicesu_m,v_min[0,N); otherwise raiseValueError.Falsebond-mask rows may contain arbitrary integer endpoints. An atom mask ofTruemeans real/valid. - Effective bond validity is
q_{m} = \mathrm{bond\_mask}[m]ANDatom_mask[u_{m}]ANDatom_mask[v_{m}]. Thus a listed bond touching a padded atom is ignored. - Parallel bond slots are permitted and preserve separate identities; they are a graph-indexing stress test,
not a claim that chemical bond orders are represented as parallel edges. No sorting or deduplication occurs. Retain
D=2Mslots in input order: edge2 misu_{m} \to v_{m}and edge2m+1isv_{m} \to u_{m}. - Return
edge_index[2,D], whose first row is source and second row destination; invalid columns are both zero. Returnreverse_index[D]withr(2m)=2m+1andr(2m+1)=2m, including invalid slots. Returnedge_mask[D]with eachq_mrepeated twice. - An invalid index zero is only a sentinel: when
N=0it does not denote a real atom. - For each valid directed edge
earising fromm,\mathrm{edge\_features}[e]=F[m]and\mathrm{edge\_inputs}[e]=\operatorname{concat}(X[\mathrm{source}(e)], F[m])along the last axis. Both floating rows are exactly zero for invalid edges. - Return
\mathrm{in\_degree}[v] = \mathrm{number}of valid directed edges whose destination isv, asint64[N]. Isolated and padded atoms have zero degree. WhenM=0, outputs have their stated empty edge dimensions;N=0also permitsM>0provided everybond_maskentry isFalse. - Empty/all-masked floating outputs retain graph-connected zero derivatives to both descriptor tensors. All finite masked feature payloads are ignored. There are no configuration keys. Chemical parsing, aromaticity/valence rules, and categorical descriptor encoding are not performed here; their prepared outputs are supplied inputs.
Why this matters
Directed message passing needs to know both where an edge starts and which single edge is its reverse, even when bonds share endpoints.
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 directed_bond_graph(
atom_features: torch.Tensor, # (N, A)
bond_features: torch.Tensor, # (M, B)
bonds: torch.Tensor, # (M, 2)
atom_mask: torch.Tensor, # (N,)
bond_mask: torch.Tensor, # (M,)
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
edge_index: (2, 2M).
reverse_index: (2M).
edge_mask: (2M).
edge_features: (2M, B).
edge_inputs: (2M, A+B).
in_degree: (N,)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement directed_bond_graph")
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.