Soft-core repair
The constraint
An alchemical transformation grows one singly attached region of a molecule into another. The final soft-core on each side must therefore be connected and attach to the common core through exactly one bond. A mapper, however, is free to return a correspondence whose unmapped atoms fall into several disconnected pieces or form a bridge between two common-core atoms. Benzene to para-xylene is the everyday fragmented example: two hydrogens on opposite sides of the ring vanish, giving two separate soft-core regions on one side and two appearing methyls on the other. No single transformation can run that.
So the partition must either be repaired or the edge rejected.
The algorithm
Repair works by demoting common-core atoms into the soft-core until the pieces join up. Choosing which atoms is a node-weighted Steiner tree problem: the soft-core fragments are the terminals, the bond graph is the network, and the cost of recruiting an atom is how much soft-core that recruitment ultimately drags in.
Three closure rules run to a fixpoint after every recruitment:
- (a) whole-ring
A ring is never left half soft-core. Touching one ring atom absorbs the ring. Fused systems then cascade on their own, with no special case: absorbing one ring pulls in the atoms it shares with its neighbours, which makes those neighbours intersected-but-not-contained on the next sweep.
- (b) hydrogen-follows-parent
A hydrogen joins the soft-core when its heavy parent does. Deliberately one-way. A soft-core hydrogen whose parent is common core stays put as a one-atom region – that asymmetry is not an oversight.
R-H -> R-CH3is the single most common transformation in the field, and its soft-core on theR-Hside is exactly one hydrogen attached to a core carbon. A two-way rule would demote that carbon, then its ring, and destroy the edge.- (c) mapped-partner
Demoting an atom demotes whatever it is mapped to. This is the only rule coupling the two molecules, and it is why the repair is genuinely joint: fixing a fragmentation on side 1 can create a new one on side 2, which the loop must then fix in turn.
Why closure size is the cost
The cost of demoting an atom is measured as the size of the closure that demotion triggers, across both molecules. That single number makes the search behave chemically with no hand-tuned per-element weight table:
a hydrogen costs about 2 – itself and its partner;
a peripheral heavy atom costs a little more;
an aromatic carbon costs its entire fused ring system, plus every attached hydrogen, plus all of their partners on the other side.
So the solver routes around rings whenever an acyclic path exists, and only pays for a ring when there is no alternative.
Termination
Both soft-core sets grow monotonically within finite atom sets, and any iteration that
does not return adds at least one atom – a bridge joining two or more fragments must
contain an atom that is not already soft-core. The loop therefore terminates in at most
n_atoms_1 + n_atoms_2 iterations.
Rejection
Checks run in order of severity, so the reported reason is the most fundamental problem rather than whichever threshold happened to be tightest:
Condition |
Reason |
|---|---|
The soft-core covers a whole molecule |
|
Fewer than |
|
Heavy soft-core exceeds |
|
Heavy soft-core exceeds |
|
A soft-core region has more than one common-core attachment bond |
|
The loop is exhausted |
|
A rejected edge is returned unchanged, never silently mutated, and is retained on
candidates. Those rejections are what explain a
sparse or disconnected network afterwards.
The ring cascade is real
Note
Because the whole-ring rule cascades through fused systems, a repair that begins with a two-atom bridge on a polycyclic scaffold can end with thirty soft-core atoms and be rejected. This is correct – there genuinely is no single-region transformation there – but it will remove many candidate edges on fused scaffolds.
Mitigations: the closure-size cost steers the solver away from ring atoms in the first
place; --show-rejected and rbfenet inspect make every rejection visible;
--ring-policy none is the escape hatch for deliberate ring-opening work.
Auditing a repair
rbfenet inspect --network network.json --edge "bza_CF3~bza_Et" --show-repair-trace
regions (3, 3) -> (1, 1)
repair trace
initial: 3 soft-core region(s) on side 1, 3 on side 2
iter 1 side 1: bridged 3 regions by demoting 1 atom(s) [1]
iter 1 side 2: bridged 3 regions by demoting 1 atom(s) [1]
final: 1/1 region(s), soft-core 4/7 atom(s)
A bridge found by the approximate Steiner solver is marked [steiner:approximate] in
the trace. It is still valid, but it is not guaranteed reproducible across networkx
versions, and a user comparing two runs deserves to know which is which.