Intermediate ligands
Some pairs cannot be related by any mapping. The soft-core would be too large, the cores too dissimilar, the geometry irreconcilable. The pipeline can invent a molecule that sits between them and turn one impossible edge into two possible ones.
Where the stage sits, which gaps it is offered, and how it interacts with every other knob is Network selection. This page is about the molecules themselves: who proposes them, how they are posed, what certifies the pose, and what leaves the package once one exists.
Read this first
An intermediate is slower on a pair that already works. IMERGE measured paths through an intermediate converging roughly 20% more slowly than the direct path. Two calculations replace one, and each one is not free.
The win is not speed and it is not accuracy on an easy pair. It is the pairs where the
direct path does not converge at all – a transformation the mapper refuses, a soft-core
too large to sample, a core the geometry cannot hold fixed. There, the choice is not
“one calculation or two”; it is “two calculations or no comparison”. That is why the
feature is off by default and why mode="bridge" – only gaps that actually disconnect
the network – is the mode to reach for first.
The generators
pairmap (default)
PairMapGenerator, after
K. Furui, S. Shimizu, Y. Akiyama, S. Kimura, T. Terada and M. Ohue, “PairMap: An Intermediate Scaffold-Based Approach to Improve Alchemical Free Energy Calculations for Complex Perturbations”, J. Chem. Inf. Model. 2025, 65, 705-721, doi:10.1021/acs.jcim.4c01634. Reference implementation: github.com/ohuelab/PairMap, CC-BY 4.0.
The implementation here is re-derived from the paper, not adapted from that code.
It emits a subnetwork, not a chain. Every position on the shared core offers three groups – the source parent’s, the target parent’s, and nothing, which is the MCS itself – and one group per position is a molecule. Two such molecules are linked with score
where \(\Delta\) counts the heavy atoms that disappear plus those that appear. That is
the LOMAP similarity, which is why the paper’s \(\beta\) and this package’s
beta = 0.1 are the same constant.
A path is scored by the harmonic mean of its squared link scores divided by its length,
and that expression collapses exactly to \(1 / \sum_i s_i^{-2}\). So the paper’s first
two subnetwork requirements – shortest path, highest link scores – are one
shortest-path search rather than two objectives to trade off. Paths of a single link are
excluded, and the direct link is not in the graph at all: it is the transformation the
pipeline already rejected. Cycles are then closed around the chosen path, one uncovered
link at a time and cheapest first, until every link sits in a cycle of at most
max_cycle – the remaining two requirements, shortest cycles and minimal redundancy.
The smallest shape it emits is A-M1-B-M2-A: two independent routes across one gap,
whose closure error is a genuine consistency check on a pair that had no direct edge at
all.
What is not covered. The paper enumerates atom- and ring-level operations – change an element, add or delete an atom, open or close a ring – so it can pass through intermediates that live inside a substituent. This implementation’s operations are whole substituents. On a pair differing by several R-groups, which is the common hard case, the two enumerations agree on the useful molecules. On a scaffold hop, or a change buried in the middle of one substituent, this generator finds the truncation to the shared core and nothing finer, and will often refuse the gap outright – saying so in the record rather than proposing something worse.
fragment-swap
FragmentSwapGenerator: one hybrid
per differing position, no search, no subnetwork. It plays the role
IdentityMapper plays for mappers –
its output is obvious by inspection, so a surprise downstream of it is unambiguously
downstream.
Posing, and the only thing that certifies it
A generator proposes a molecule with no conformer. Posing is centralised in
rbfenetmap.core.posing.pose_intermediate() for the same reason descriptors are: a
generator that posed its own output would make an intermediate’s quality depend on which
plugin invented it.
An intermediate is a hybrid, and every heavy atom of it corresponds to a specific atom of
a parent whose pose is already right in the binding-site frame. So the poser seeds a
conformer from the parents’ coordinates through the generator’s parent_atom_map,
embeds under a coordMap, restores exactness with a restrained minimisation, and fits
rigidly back onto the donor coordinates. Neither ConstrainedEmbed (which discards a
known-good pose for everything outside the scaffold) nor rbfenetmap.core.align
(which recovers a frame for molecules that already have good conformers) does that.
Failures are data: PoseResult carries a rejection –
EMBED_FAILED, POSE_RMSD_EXCEEDED, CHARGE_MISMATCH, STEREO_UNDEFINED – and
never raises.
Important
You do not have to trust the poser. A synthetic ligand is an ordinary vertex, and
the in-place core_rmsd gate on its A~M and M~B sub-edges is a complete check
on whether it is posed in its parents’ frame. A badly posed intermediate comes back as
an ordinary core_geometry_mismatch, the proposal fails to close the gap, and it is
dropped whole. The poser’s job is to make that check pass often, not to make it
unnecessary.
Provenance
Every invented ligand carries a LigandProvenance: the
generator, the parents, the pose method, and the pose RMSD. It is a field on
Ligand rather than a subclass, because
networkio._ligand_from_dict constructs Ligand directly and a subclass would silently
downgrade on round-trip. It is omitted entirely from the serialized form when None, so
an all-real network is byte-identical to what it was before the feature existed.
network.intermediates records every gap attempted, accepted or not, with the
rejection and the trace. Without it, a network where generation was tried and failed is
indistinguishable from one where it was never switched on – and those call for entirely
different responses.
What leaves the package
Amber export
edges.dat names residues, and BuildEdges needs a parameterised topology for every
one of them. An invented ligand exists only inside the planned network; nobody has seen it,
and nothing on disk describes it. So:
every ligand is written as
ligands/<name>.sdf. The real ones too, because “every name inedges.dathas a structure inligands/“ is an invariant a setup script can assert, while “every name except the ones you already had” is not;invented ligands are listed in
intermediates.txtas<name> <parent> <parent> <generator>, so a setup script knows which residues to parameterise first;rbfenet plan --validate-exporter amberwarns with the count before the mapping run, and warns pre-flight when generation is merely enabled.
HTML report and GraphML
An invented vertex is drawn with a dashed outline and its label ends in SYN; every edge
card naming one carries a SYN badge. Never colour alone – the same rule the CBFE badge
follows. An Invented ligands section lists parents, generator, pose method and pose RMSD;
the RMSD is measured with the same core_rmsd() the
feasibility gate uses, so it reads directly against core_rmsd_threshold. The summary
counts real ligands and invented ones separately, because “12 ligands” quietly meaning nine
is the misreading that costs somebody a simulation.
to_networkx() puts synthetic on every node, so
GraphML carries the distinction too.
Tuning
The five search constants keep the paper’s names and published defaults, on
IntermediateOptions and on the command line.
Option / flag |
Default |
What it bounds |
|---|---|---|
|
|
Weakest link worth proposing. At |
|
|
Links on the source-to-target path. At least 2: one link is the rejected direct edge. |
|
|
Largest cycle built to give a link a second, independent route. |
|
|
How far from either parent, in links, a molecule may sit and still join the subnetwork. |
|
|
Decay of the link score per heavy atom changed. |
max_molecules (--intermediates-per-gap) is the one to reach for first: it is what
stops a heavily decorated pair from spending the whole run on embeddings. The path is paid
for before any cycle is closed, so tightening it costs redundancy before it costs the
bridge.