MolecularDiffusion.data.component.reaction_data¶
A shared, model-neutral container for a chemical reaction.
Transition-state generators all take the same thing – a reactant side, a
product side, and a transition state to predict – and disagree only about
which channel each side is carried in. This module is that shape, and
nothing more. It imports the platform’s canonical bond vocabulary and
nothing from modules/, so a model-specific data layer can sit beside it
(oareactdiff_data.py) without either one owning the other.
Three real reaction models were read at source to size this contract; every field below is demanded by at least one of them.
one shared atom ordering |
yes |
yes |
yes |
TS target |
3D coords |
3D coords |
3D coords |
reactant/product side is |
geometry |
connectivity |
connectivity |
their 3D endpoints are used |
yes |
no |
no |
bonds |
none |
22-class |
9-class |
stereo |
none |
vestigial |
bond classes |
Only OA-ReactDiff is implemented in this pass. The other two are recorded because they are what makes the shape a contract rather than one model’s struct with a general-sounding name.
Two rules that are not fields¶
``bond_index`` is DIRECTED, and per side. Deliberately not the
upper-triangular storage rule of graph3d_dataset.py. RitS encodes
tetrahedral chirality as an antisymmetric directed 3-cycle a -> c -> b ->
a whose orientation is the only thing distinguishing R from S; symmetrising
or canonicalising to i < j would silently racemize a whole corpus.
Per-side rather than one shared union index, because a side has to be
self-describing – the union edge set both siblings actually feed their
networks is (bmat_r + bmat_p).nonzero(), derived in their collate.
``pos`` is real geometry or it is ``None``. Never a zero placeholder,
never a copy of the TS standing in for a missing endpoint. Both patterns
exist upstream in RitS, and a container that stored them would make
ReactionSide.has_geometry lie. The rule costs nothing and removes the
need for a per-slot validity flag.
Two PyG gotchas the layout dodges, both found live in GoFlow: PyG
node-offsets any attribute whose name contains "index", so scalar
metadata lives in Reaction.meta and is never named *_index; and
PyG deletes attributes assigned None, so optional fields need presence
checks rather than null checks once they reach a Data object.
What is deliberately absent¶
No representation enum. The populated fields are the representation;
ReactionSide.has_geometry / ReactionSide.has_connectivity
derive it in one line each. A stored enum is a second source of truth that
can desync from the tensors it describes.
No ``fc``. None of the three models carries a per-atom signed formal
charge: OA-ReactDiff has no formal charge at all, GoFlow folds
GetFormalCharge into its feat one-hot, and RitS’s charges is a
per-molecule net charge broadcast to every atom. ReactionSide.feat
and Reaction.meta cover both.
No stereo channel – yet. RitS packs E/Z and chirality into bond classes
5-8, which are not bonds and do not belong in a channel every other model
reads as bond order. The honest fix, purely additive and deferred until a
model actually populates it, is one more optional pair on
ReactionSide: stereo_index (2, S), directed, and
stereo_type (S,) over a separate {E, Z, CHI_STAR, CHI_CYCLE}
vocabulary. Recorded here so that day is an addition, not a redesign.
Classes¶
One reaction: a shared atom set, two sides, and a TS target. |
|
One side of a reaction -- reactant or product. |
Module Contents¶
- class MolecularDiffusion.data.component.reaction_data.Reaction¶
One reaction: a shared atom set, two sides, and a TS target.
zis hoisted here rather than duplicated per side because all three surveyed models guarantee a single shared atom ordering across reactant, transition state and product. Making it one tensor makes that guarantee structural instead of an assertion each model rewrites differently.ts_posis a bare tensor rather than a thirdReactionSide: no surveyed model predicts TS bonds, so the symmetry would be decoration. Promote it the day one does.- z¶
(n,)atomic numbers. This is the shared ordering – every other per-atom tensor in the reaction is aligned to it.
- reactant¶
the reactant side.
- product¶
the product side.
- ts_pos¶
(n, 3)transition-state coordinates – the prediction target.Noneat inference time, when there is nothing to predict against.
- meta¶
free-form per-reaction scalars and strings (reaction id, SMILES, energies). Never batched, and never named
*_index(PyG node-offsets any such key).
- product: ReactionSide¶
- reactant: ReactionSide¶
- ts_pos: torch.Tensor | None = None¶
- class MolecularDiffusion.data.component.reaction_data.ReactionSide¶
One side of a reaction – reactant or product.
Every field is optional because which ones are populated is the representation. A side carrying only
posis a 3D endpoint (OA-ReactDiff); a side carrying only bonds andfeatis a connectivity-only condition (GoFlow, RitS).- pos¶
(n, 3)real 3D coordinates, orNone. Never a placeholder – see the module docstring.
- bond_index¶
(2, E)directed edges, this side’s own bonds only, orNone. Not upper-triangular.
- bond_type¶
(E,)classes over the canonical five-entryBOND_VOCAB(0=none, 1=SINGLE, 2=DOUBLE, 3=TRIPLE, 4=AROMATIC), aligned withbond_index, orNone.
- feat¶
(n, F)per-atom descriptors, orNone. The vocabulary behindFis dataset-derived and travels with the model’s own data module, not with this container.
- validate(n_atoms: int) None¶
Check every populated field against the reaction’s atom count.
- Parameters:
n_atoms – length of the owning
Reaction’sz.- Raises:
ValueError – on any shape disagreement. These are all silent corruption if they get through – a mis-sized
posbroadcasts, and abond_indexout of range indexes the wrong atoms rather than erroring.
- bond_index: torch.Tensor | None = None¶
- bond_type: torch.Tensor | None = None¶
- feat: torch.Tensor | None = None¶
- pos: torch.Tensor | None = None¶