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

\[s = \exp(-\beta \Delta)\]

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 in edges.dat has a structure in ligands/ 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.txt as <name> <parent> <parent> <generator>, so a setup script knows which residues to parameterise first;

  • rbfenet plan --validate-exporter amber warns 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

min_link_score / --intermediate-min-link-score

0.2

Weakest link worth proposing. At beta = 0.1 this is about sixteen heavy atoms changing in one step.

max_dist / --intermediate-max-dist

3

Links on the source-to-target path. At least 2: one link is the rejected direct edge.

max_cycle / --intermediate-max-cycle

4

Largest cycle built to give a link a second, independent route.

max_subgraph_dist / --intermediate-max-subgraph-dist

4

How far from either parent, in links, a molecule may sit and still join the subnetwork.

beta / --intermediate-beta

0.1

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.