Skip to content

Commit 5fb53e1

Browse files
committed
Remove cross-bond dihedrals and impropers.
1 parent 8dabe75 commit 5fb53e1

2 files changed

Lines changed: 175 additions & 6 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ handle the diversity of perturbations encountered in practice:
4545
- **Ring-breaking perturbations:** adjacent bridges with independent ghost
4646
groups retain each other as physical neighbours; angles with a ghost central
4747
atom spanning two physical neighbours are replaced by a linear spacer
48-
(180°, soft force constant); and angles spanning the ring-making/breaking
49-
bond are removed in the state where that bond is absent.
48+
(180°, soft force constant); and angles and dihedrals spanning the
49+
ring-making/breaking bond are removed in the state where that bond is absent.
5050

5151
Ghostly is incorporated into the [SOMD2](https://github.com/openbiosim/somd2)
5252
free-energy perturbation engine.

src/ghostly/_ghostly.py

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -451,12 +451,18 @@ def modify(
451451
mol, ghosts0, modifications, skip_ghosts=linearised0, is_lambda1=False
452452
)
453453

454-
# Remove angles that span ring-making bonds in the state where those
455-
# bonds do not yet exist.
454+
# Remove angles, dihedrals, and impropers that span ring-making bonds
455+
# in the state where those bonds do not yet exist.
456456
if _ring_making_bonds:
457457
mol = _remove_cross_bond_angles(
458458
mol, _ring_making_bonds, modifications, is_lambda1=False
459459
)
460+
mol = _remove_cross_bond_dihedrals(
461+
mol, _ring_making_bonds, modifications, is_lambda1=False
462+
)
463+
mol = _remove_cross_bond_impropers(
464+
mol, _ring_making_bonds, modifications, is_lambda1=False
465+
)
460466

461467
# Soften any surviving mixed ghost/physical dihedrals.
462468
mol = _soften_mixed_dihedrals(
@@ -572,12 +578,18 @@ def modify(
572578
mol, ghosts1, modifications, skip_ghosts=linearised1, is_lambda1=True
573579
)
574580

575-
# Remove angles that span ring-breaking bonds in the state where those
576-
# bonds no longer exist.
581+
# Remove angles, dihedrals, and impropers that span ring-breaking bonds
582+
# in the state where those bonds no longer exist.
577583
if _ring_breaking_bonds:
578584
mol = _remove_cross_bond_angles(
579585
mol, _ring_breaking_bonds, modifications, is_lambda1=True
580586
)
587+
mol = _remove_cross_bond_dihedrals(
588+
mol, _ring_breaking_bonds, modifications, is_lambda1=True
589+
)
590+
mol = _remove_cross_bond_impropers(
591+
mol, _ring_breaking_bonds, modifications, is_lambda1=True
592+
)
581593

582594
# Soften any surviving mixed ghost/physical dihedrals.
583595
mol = _soften_mixed_dihedrals(
@@ -2508,6 +2520,163 @@ def _remove_cross_bond_angles(mol, changing_bonds, modifications, is_lambda1=Fal
25082520
return mol
25092521

25102522

2523+
def _remove_cross_bond_dihedrals(mol, changing_bonds, modifications, is_lambda1=False):
2524+
r"""
2525+
Remove dihedral terms whose central bond spans a ring-making or
2526+
ring-breaking bond in the end state where that bond does not exist.
2527+
Such dihedrals encode the bonded geometry and couple atoms across the
2528+
missing bond, causing poor overlap between adjacent lambda windows.
2529+
2530+
A - B ~~~ C - D
2531+
(missing bond)
2532+
2533+
If the B-C bond is absent in this end state, the dihedral A-B-C-D
2534+
is removed.
2535+
2536+
Parameters
2537+
----------
2538+
2539+
mol : sire.mol.Molecule
2540+
The perturbable molecule.
2541+
2542+
changing_bonds : set of (int, int)
2543+
Pairs of atom index values for bonds that change between end states.
2544+
Pass ring-making bonds for is_lambda1=False, ring-breaking bonds for
2545+
is_lambda1=True.
2546+
2547+
modifications : dict
2548+
A dictionary to store details of the modifications made.
2549+
2550+
is_lambda1 : bool, optional
2551+
Whether to modify dihedrals at lambda = 1.
2552+
2553+
Returns
2554+
-------
2555+
2556+
mol : sire.mol.Molecule
2557+
The updated molecule.
2558+
"""
2559+
2560+
if not changing_bonds:
2561+
return mol
2562+
2563+
info = mol.info()
2564+
2565+
if is_lambda1:
2566+
mod_key = "lambda_1"
2567+
suffix = "1"
2568+
else:
2569+
mod_key = "lambda_0"
2570+
suffix = "0"
2571+
2572+
dihedrals = mol.property("dihedral" + suffix)
2573+
new_dihedrals = _SireMM.FourAtomFunctions(mol.info())
2574+
modified = False
2575+
2576+
for p in dihedrals.potentials():
2577+
idx0 = info.atom_idx(p.atom0())
2578+
idx1 = info.atom_idx(p.atom1())
2579+
idx2 = info.atom_idx(p.atom2())
2580+
idx3 = info.atom_idx(p.atom3())
2581+
2582+
i, j, k, l = idx0.value(), idx1.value(), idx2.value(), idx3.value()
2583+
central = (min(j, k), max(j, k))
2584+
2585+
if central in changing_bonds:
2586+
_logger.debug(
2587+
f" Removing cross-bond dihedral: [{i}-{j}-{k}-{l}], {p.function()}"
2588+
)
2589+
modifications[mod_key]["removed_dihedrals"].append(f"{i},{j},{k},{l}")
2590+
modified = True
2591+
else:
2592+
new_dihedrals.set(idx0, idx1, idx2, idx3, p.function())
2593+
2594+
if modified:
2595+
mol = (
2596+
mol.edit()
2597+
.set_property("dihedral" + suffix, new_dihedrals)
2598+
.molecule()
2599+
.commit()
2600+
)
2601+
2602+
return mol
2603+
2604+
2605+
def _remove_cross_bond_impropers(mol, changing_bonds, modifications, is_lambda1=False):
2606+
"""
2607+
Remove improper dihedral terms that involve both atoms of a ring-making or
2608+
ring-breaking bond in the end state where that bond does not exist.
2609+
2610+
Parameters
2611+
----------
2612+
2613+
mol : sire.mol.Molecule
2614+
The perturbable molecule.
2615+
2616+
changing_bonds : set of (int, int)
2617+
Pairs of atom index values for bonds that change between end states.
2618+
2619+
modifications : dict
2620+
A dictionary to store details of the modifications made.
2621+
2622+
is_lambda1 : bool, optional
2623+
Whether to modify impropers at lambda = 1.
2624+
2625+
Returns
2626+
-------
2627+
2628+
mol : sire.mol.Molecule
2629+
The updated molecule.
2630+
"""
2631+
2632+
if not changing_bonds:
2633+
return mol
2634+
2635+
info = mol.info()
2636+
2637+
if is_lambda1:
2638+
mod_key = "lambda_1"
2639+
suffix = "1"
2640+
else:
2641+
mod_key = "lambda_0"
2642+
suffix = "0"
2643+
2644+
impropers = mol.property("improper" + suffix)
2645+
new_impropers = _SireMM.FourAtomFunctions(mol.info())
2646+
modified = False
2647+
2648+
for p in impropers.potentials():
2649+
idx0 = info.atom_idx(p.atom0())
2650+
idx1 = info.atom_idx(p.atom1())
2651+
idx2 = info.atom_idx(p.atom2())
2652+
idx3 = info.atom_idx(p.atom3())
2653+
2654+
atoms = {idx0.value(), idx1.value(), idx2.value(), idx3.value()}
2655+
2656+
if any(a in atoms and b in atoms for a, b in changing_bonds):
2657+
_logger.debug(
2658+
f" Removing cross-bond improper: "
2659+
f"[{idx0.value()}-{idx1.value()}-{idx2.value()}-{idx3.value()}], "
2660+
f"{p.function()}"
2661+
)
2662+
modifications[mod_key]["removed_dihedrals"].append(
2663+
f"{idx0.value()},{idx1.value()},{idx2.value()},{idx3.value()}"
2664+
)
2665+
modified = True
2666+
else:
2667+
new_impropers.set(idx0, idx1, idx2, idx3, p.function())
2668+
2669+
if modified:
2670+
mol = (
2671+
mol.edit()
2672+
.set_property("improper" + suffix, new_impropers)
2673+
.molecule()
2674+
.commit()
2675+
)
2676+
2677+
return mol
2678+
2679+
25112680
def _soften_mixed_dihedrals(
25122681
mol, ghosts, modifications, soften_anchors=1.0, is_lambda1=False
25132682
):

0 commit comments

Comments
 (0)