@@ -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+
25112680def _soften_mixed_dihedrals (
25122681 mol , ghosts , modifications , soften_anchors = 1.0 , is_lambda1 = False
25132682):
0 commit comments