@@ -1760,76 +1760,58 @@ namespace SireIO
17601760
17611761 SireBase::PropertyList mergeIntrascale (const CLJNBPairs &nb0,
17621762 const CLJNBPairs &nb1,
1763- const MoleculeInfoData &merged_info,
1763+ const Connectivity &conn0,
1764+ const Connectivity &conn1,
1765+ const CLJScaleFactor &sf14,
17641766 const QHash<AtomIdx, AtomIdx> &mol0_merged_mapping,
17651767 const QHash<AtomIdx, AtomIdx> &mol1_merged_mapping)
17661768 {
1767- // Helper lambda: copy scaling factors from 'nb' to 'nb_merged' according
1768- // to the provided mapping. Takes nb_merged by reference to avoid copies.
1769- // When copy_all is true, ALL pairs between mapped atoms are written
1770- // (including the default (1,1)), so that the correct end-state values
1771- // always overwrite any values set by a prior ghost-topology pass.
1772- // When copy_all is false, only non-(1,1) pairs are written (sufficient
1773- // for the first/ghost pass, where unset pairs default to (1,1) anyway).
1774- auto copyIntrascale = [&](const CLJNBPairs &nb, CLJNBPairs &nb_merged,
1775- const QHash<AtomIdx, AtomIdx> &mapping,
1776- bool copy_all = false )
1769+ // Build base CLJNBPairs from the per-state merged connectivity. This
1770+ // correctly captures bonded distances (1-2, 1-3, 1-4) in the merged
1771+ // atom space, including paths that only exist in one end state (e.g.
1772+ // the ring-closure bond for ring-breaking perturbations).
1773+ CLJNBPairs intra0 (conn0, sf14);
1774+ CLJNBPairs intra1 (conn1, sf14);
1775+
1776+ // Override with per-pair values from the individual molecule intrascales.
1777+ // For standard AMBER molecules these are identical to the connectivity-
1778+ // derived values and the override is a no-op. For force fields with
1779+ // non-default per-pair scale factors (e.g. GLYCAM funct=2 with (1,1)
1780+ // instead of global sf14 for certain 1-4 pairs) the override replaces
1781+ // the global-sf14 value set by the base construction with the correct
1782+ // per-pair value.
1783+ auto overrideIntrascale = [&](const CLJNBPairs &nb, CLJNBPairs &nb_merged,
1784+ const QHash<AtomIdx, AtomIdx> &mapping)
17771785 {
17781786 const int n = nb.nAtoms ();
17791787
17801788 for (int i = 0 ; i < n; ++i)
17811789 {
17821790 const AtomIdx ai (i);
1783-
1784- // Get the index of this atom in the merged system.
17851791 const AtomIdx merged_ai = mapping.value (ai, AtomIdx (-1 ));
17861792
1787- // If this atom hasn't been mapped to the merged system, then we
1788- // can skip it, as any scaling factors involving this atom will
1789- // just use the default.
17901793 if (merged_ai == AtomIdx (-1 ))
17911794 continue ;
17921795
17931796 for (int j = i; j < n; ++j)
17941797 {
17951798 const AtomIdx aj (j);
1799+ const AtomIdx merged_aj = mapping.value (aj, AtomIdx (-1 ));
17961800
1797- // Get the scaling factor for this pair of atoms.
1798- const CLJScaleFactor sf = nb. get (ai, aj) ;
1801+ if (merged_aj == AtomIdx (- 1 ))
1802+ continue ;
17991803
1800- // Copy if this is a non-default scaling factor, or if
1801- // copy_all is set (second pass: must overwrite ghost-topology
1802- // values even when the correct end-state value is (1,1)).
1803- if (copy_all or sf.coulomb () != 1.0 or sf.lj () != 1.0 )
1804- {
1805- // Get the index of the second atom in the merged system.
1806- const AtomIdx merged_aj = mapping.value (aj, AtomIdx (-1 ));
1807-
1808- // Only set the scaling factor if both atoms have been
1809- // mapped to the merged system. If one of the atoms
1810- // hasn't been mapped, then we can just use the default.
1811- if (merged_aj != AtomIdx (-1 ))
1812- nb_merged.set (merged_ai, merged_aj, sf);
1813- }
1804+ const CLJScaleFactor nb_sf = nb.get (ai, aj);
1805+ const CLJScaleFactor base_sf = nb_merged.get (merged_ai, merged_aj);
1806+
1807+ if (nb_sf.coulomb () != base_sf.coulomb () or nb_sf.lj () != base_sf.lj ())
1808+ nb_merged.set (merged_ai, merged_aj, nb_sf);
18141809 }
18151810 }
18161811 };
18171812
1818- // Create the intrascale objects for the merged end-states.
1819- CLJNBPairs intra0 (merged_info);
1820- CLJNBPairs intra1 (merged_info);
1821-
1822- // Copy scaling factors from the original intrascale objects to the
1823- // merged intrascale objects. For each end state, the ghost molecule's
1824- // topology is written first (non-default pairs only), then the correct
1825- // end-state topology overwrites with copy_all=true so that (1,1) pairs
1826- // are also written. This handles ring-breaking perturbations where a
1827- // pair that is excluded/1-4 in one state becomes fully interacting (1,1)
1828- // in the other state and must not be left at the ghost state's value.
1829- copyIntrascale (nb1, intra0, mol1_merged_mapping);
1830- copyIntrascale (nb0, intra0, mol0_merged_mapping, true );
1831- copyIntrascale (nb0, intra1, mol0_merged_mapping);
1832- copyIntrascale (nb1, intra1, mol1_merged_mapping, true );
1813+ overrideIntrascale (nb0, intra0, mol0_merged_mapping);
1814+ overrideIntrascale (nb1, intra1, mol1_merged_mapping);
18331815
18341816 // Assemble the intrascale objects into a property list to return.
18351817 SireBase::PropertyList ret;
0 commit comments