Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
3e848a6
first pass at abstract classes
IAlibay Dec 4, 2024
ef050e5
A start at restraints and forces
IAlibay Dec 5, 2024
420f3e5
Add boresch restraint class
IAlibay Dec 6, 2024
2e2b52e
Fix units
IAlibay Dec 6, 2024
76c5fcf
Fix correction return in kj/mole
IAlibay Dec 9, 2024
e9cd918
Add more restraint API bits
IAlibay Dec 9, 2024
f1bbd8a
move some things around
IAlibay Dec 9, 2024
4f4d58e
Merge branch 'main' into omm-restraints
IAlibay Dec 9, 2024
ac452e9
Some changes
IAlibay Dec 11, 2024
536a76e
Merge branch 'omm-restraints' of github.com:OpenFreeEnergy/openfe int…
IAlibay Dec 11, 2024
a19c86c
refactor restraints
IAlibay Dec 12, 2024
20dd1dc
add some angle checks
IAlibay Dec 12, 2024
9ab74a8
only construct with settings
IAlibay Dec 12, 2024
8f2e1e0
Add more checks to utilities
IAlibay Dec 12, 2024
0a480aa
host finding code
IAlibay Dec 12, 2024
7a7be90
fix up weird black wrapping
IAlibay Dec 12, 2024
733f3b3
remove old search file, add more changes to boresch search
IAlibay Dec 12, 2024
bbef017
Merge branch 'main' into omm-restraints
IAlibay Dec 12, 2024
96decff
Remove duplicate methods
IAlibay Dec 12, 2024
2d97de8
Apply suggestions from code review
IAlibay Dec 12, 2024
116ba64
Add some more docstring
IAlibay Dec 12, 2024
9ae60da
Add minimized vectors on the collinear checks
IAlibay Dec 13, 2024
3cce308
add host atom finding routine
IAlibay Dec 14, 2024
9171d39
autoformatting
IAlibay Dec 14, 2024
033a1e4
various fixes
IAlibay Dec 14, 2024
fe1308e
docstring drive
IAlibay Dec 14, 2024
d71b961
Migrate to restraint_utils
IAlibay Dec 15, 2024
c914b18
base for restraint settings
IAlibay Dec 16, 2024
d24a5a5
Fix up some things
IAlibay Dec 16, 2024
4dd16af
Add restraint settings
IAlibay Dec 16, 2024
ec0d01d
Add missing settings imports
IAlibay Dec 16, 2024
e19db65
Settings and some tests for them
IAlibay Dec 17, 2024
854d1c6
negative idxs test
IAlibay Dec 17, 2024
9b53cd2
addressing some mypy issues
IAlibay Dec 17, 2024
de35e3f
Addressing reviews
IAlibay Dec 20, 2024
7c502c7
Add a todo from last year
IAlibay Jan 7, 2025
786550c
Merge branch 'main' into omm-restraints
IAlibay Jan 20, 2025
fc1f316
Update to address review comments
IAlibay Jan 21, 2025
98ff0c8
Last review comment
IAlibay Jan 21, 2025
e39cdd3
Various fixes
IAlibay Jan 22, 2025
7906641
Now with DSSP!
IAlibay Jan 24, 2025
a52f93b
the big refactor
IAlibay Jan 24, 2025
e166c70
fix imports
IAlibay Jan 24, 2025
f89a8c8
Fix up secondary structure filtering & add chain selection
IAlibay Jan 26, 2025
2af07c8
Add angle wrapping method
IAlibay Jan 26, 2025
aa6f338
Add more tests
IAlibay Jan 26, 2025
3428b39
Add tests for heavy atom getter
IAlibay Jan 26, 2025
d2b0a3d
Add extra test
IAlibay Jan 26, 2025
3ef083d
Add some extra tests
IAlibay Jan 26, 2025
7eb8656
Add collinearity tests & better explanation of code
IAlibay Jan 27, 2025
e911211
Add tests and fix a few things
IAlibay Jan 27, 2025
0c2f102
Angle variance tests
IAlibay Jan 27, 2025
11aacc8
Some changes
IAlibay Feb 3, 2025
dc1d3dd
Try something
IAlibay Feb 13, 2025
04481fa
Fixes nojump issue
IAlibay Feb 18, 2025
ca2d5eb
Add atom sorting class
IAlibay Feb 18, 2025
9e76495
Add missing import
IAlibay Feb 18, 2025
cb8e928
Merge branch 'omm-restraints' of github.com:OpenFreeEnergy/openfe int…
IAlibay Feb 18, 2025
6b96929
fixes various imports and things
IAlibay Feb 19, 2025
a95247e
Oops walked away from my laptop without committing
IAlibay Feb 19, 2025
b19ce45
Add the base case for rmsf when there is only one frame
IAlibay Feb 24, 2025
e8189d5
fix docs
IAlibay Feb 24, 2025
221cfc8
some settings cleanup
IAlibay Feb 24, 2025
d71872d
Fix some tests
IAlibay Feb 27, 2025
318ea20
Merge branch 'main' into omm-restraints
IAlibay Mar 2, 2025
2f2dfa1
Fix up settings typing
IAlibay Mar 2, 2025
1b30231
Some typing improvements
IAlibay Mar 2, 2025
0d3519b
try NDArray typing
IAlibay Mar 2, 2025
01a326e
Some more typing fixes
IAlibay Mar 2, 2025
465f7af
more typing stuff
IAlibay Mar 2, 2025
066702d
Fix up more typing
IAlibay Mar 2, 2025
3b60b62
more typing fixes
IAlibay Mar 2, 2025
7fe0a44
variable length in is_collinear
IAlibay Mar 2, 2025
d8ef5a1
this is something we should fix upstream, ignore for now
IAlibay Mar 2, 2025
4aa676e
Fix some typing
IAlibay Mar 2, 2025
4170fde
fix up variable length on list
IAlibay Mar 2, 2025
4110584
try fixing type hinting
IAlibay Mar 2, 2025
4298313
fix rename issue
IAlibay Mar 2, 2025
d5f5137
make things into list for typing purposes
IAlibay Mar 2, 2025
cee01d4
more type hinting fixes
IAlibay Mar 2, 2025
28393f3
try to get the mixin super to be accepted by mypy
IAlibay Mar 2, 2025
a22f0bc
remove type hint in abc to avoid issues
IAlibay Mar 2, 2025
5353dd7
again remove the top level hint for mypy
IAlibay Mar 2, 2025
f96da71
fix missing controlling parameter name
IAlibay Mar 2, 2025
dacc57a
again remove abc type hint to avoid mypy issues
IAlibay Mar 2, 2025
140f33f
Deal with flat bottom restraint type hint
IAlibay Mar 2, 2025
2dae061
try to avoid Mixin mypy issues
IAlibay Mar 2, 2025
4f1f427
more ignores
IAlibay Mar 2, 2025
a73e245
Add another override
IAlibay Mar 2, 2025
3469e52
Merge branch 'main' into omm-restraints
IAlibay Mar 4, 2025
bf61539
Merge branch 'main' into omm-restraints
IAlibay Mar 13, 2025
a63ebfe
simplify restraint validation and general clean up
jthorton Apr 2, 2025
b048a56
Merge branch 'main' into omm-restraints
jthorton Apr 2, 2025
1f028f8
fix type, add harmonic geometry tests
jthorton Apr 3, 2025
6a09412
add flatbottom geometry tests
jthorton Apr 3, 2025
80b1bb3
fix fixture construction, add boresh geometry tests, fix get geometry
jthorton Apr 4, 2025
997ba7f
fix naming, fix heavy atom guest selection, add host and guest tests
jthorton Apr 4, 2025
6945c79
add utils tests using trajectories, don't upload yet
jthorton Apr 7, 2025
02c64e5
add all tests ready for zenodo files
jthorton Apr 14, 2025
1e13f05
use zenodo test data
jthorton Apr 14, 2025
45392ca
Merge branch 'main' into omm-restraints
jthorton Apr 14, 2025
09c03f2
update to use pooch system file
jthorton Apr 14, 2025
b34e9a1
Merge branch 'main' into omm-restraints
IAlibay Apr 15, 2025
2b79724
Merge branch 'main' into omm-restraints
hannahbaumann Apr 23, 2025
05f9679
Merge branch 'main' into omm-restraints
IAlibay Apr 28, 2025
6f81295
Update settings.py
IAlibay Apr 28, 2025
3c2c1a7
Merge branch 'main' into omm-restraints
IAlibay May 1, 2025
e4adb62
Merge branch 'main' into omm-restraints
hannahbaumann Jun 4, 2025
ae18dc8
Merge branch 'main' into omm-restraints
hannahbaumann Jun 6, 2025
08de456
fix shortest_path for new networkx API
IAlibay Jun 6, 2025
bb5479b
Update settings.py
IAlibay Jun 13, 2025
0edefbe
Update boresch corr test after settings update
hannahbaumann Jun 16, 2025
7c45637
Apply suggestions from code review
IAlibay Jun 18, 2025
ce3ce66
Merge branch 'main' into omm-restraints
hannahbaumann Jun 23, 2025
5c80f29
Merge branch 'main' into omm-restraints
hannahbaumann Jun 26, 2025
60dd56e
move to the models vendor
IAlibay Jun 26, 2025
dcf3b9c
various mypy fixes
IAlibay Jun 26, 2025
77a5d95
Deal wwith assignment type issues
IAlibay Jun 26, 2025
b242693
remove some uncessary type ignores
IAlibay Jun 26, 2025
42e43ba
Just remove typing on base class
IAlibay Jun 26, 2025
2a1a1e0
type of class not class
IAlibay Jun 26, 2025
5433f68
fix models imports
IAlibay Jun 26, 2025
dc45bf0
Merge branch 'main' into omm-restraints
IAlibay Jun 26, 2025
9e41472
Add news item
IAlibay Jun 26, 2025
3b4447c
clean up settings
IAlibay Jun 27, 2025
376c1db
Merge branch 'main' into omm-restraints
IAlibay Jul 1, 2025
89a546b
Update openfe/protocols/openmm_rfe/equil_rfe_methods.py
IAlibay Jul 4, 2025
01eb108
isort and black
IAlibay Jul 4, 2025
27447b3
isort + black
IAlibay Jul 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/restraints_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Adds a new internal API for defining alchemical restraints (PR #1043).

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
Empty file.
4 changes: 4 additions & 0 deletions openfe/protocols/restraint_utils/geometry/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .base import BaseRestraintGeometry, HostGuestRestraintGeometry
from .boresch import BoreschRestraintGeometry
from .flatbottom import FlatBottomDistanceGeometry
from .harmonic import DistanceRestraintGeometry
50 changes: 50 additions & 0 deletions openfe/protocols/restraint_utils/geometry/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# This code is part of OpenFE and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/openfe
"""
Restraint Geometry classes

TODO
----
* Add relevant duecredit entries.
"""
import abc

from pydantic.v1 import BaseModel, validator


class BaseRestraintGeometry(BaseModel, abc.ABC):
"""
A base class for a restraint geometry.
"""

class Config:
arbitrary_types_allowed = True


class HostGuestRestraintGeometry(BaseRestraintGeometry):
"""
An ordered list of guest atoms to restrain.

Note
----
The order matters! It will be used to define the underlying
force.
"""

guest_atoms: list[int]
"""
An ordered list of host atoms to restrain.

Note
----
The order matters! It will be used to define the underlying
force.
"""
host_atoms: list[int]

@validator("guest_atoms", "host_atoms")
def positive_idxs(cls, v):
if v is not None and any([i < 0 for i in v]):
errmsg = "negative indices passed"
raise ValueError(errmsg)
return v
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .geometry import BoreschRestraintGeometry, find_boresch_restraint
291 changes: 291 additions & 0 deletions openfe/protocols/restraint_utils/geometry/boresch/geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
# This code is part of OpenFE and is licensed under the MIT license.
# For details, see https://github.com/OpenFreeEnergy/openfe
"""
Restraint Geometry classes

TODO
----
* Add relevant duecredit entries.
"""
from typing import Optional

import MDAnalysis as mda
from gufe.vendor.openff.models.types import FloatQuantity
from MDAnalysis.lib.distances import calc_angles, calc_bonds, calc_dihedrals
from openfe.protocols.restraint_utils.geometry.base import HostGuestRestraintGeometry
from openff.units import Quantity, unit
from rdkit import Chem

from .guest import find_guest_atom_candidates
from .host import find_host_anchor, find_host_atom_candidates


class BoreschRestraintGeometry(HostGuestRestraintGeometry):
"""
A class that defines the restraint geometry for a Boresch restraint.

The restraint is defined by the following:

H2 G2
- -
- -
H1 - - H0 -- G0 - - G1

Where HX represents the X index of ``host_atoms`` and GX
the X index of ``guest_atoms``.
"""

r_aA0: FloatQuantity["nanometer"]
"""
The equilibrium distance between H0 and G0.
"""
theta_A0: FloatQuantity["radians"]
"""
The equilibrium angle value between H1, H0, and G0.
"""
theta_B0: FloatQuantity["radians"]
"""
The equilibrium angle value between H0, G0, and G1.
"""
phi_A0: FloatQuantity["radians"]
"""
The equilibrium dihedral value between H2, H1, H0, and G0.
"""
phi_B0: FloatQuantity["radians"]

"""
The equilibrium dihedral value between H1, H0, G0, and G1.
"""
phi_C0: FloatQuantity["radians"]

"""
The equilibrium dihedral value between H0, G0, G1, and G2.
"""


def _get_restraint_distances(
atomgroup: mda.AtomGroup,
) -> tuple[Quantity, Quantity, Quantity, Quantity, Quantity, Quantity]:
"""
Get the bond, angle, and dihedral distances for an input atomgroup
defining the six atoms for a Boresch-like restraint.

The atoms must be in the order of H0, H1, H2, G0, G1, G2.

Parameters
----------
atomgroup : mda.AtomGroup
An AtomGroup defining the restrained atoms in order.

Returns
-------
bond : openff.units.Quantity
The H0-G0 bond value.
angle1 : openff.units.Quantity
The H1-H0-G0 angle value.
angle2 : openff.units.Quantity
The H0-G0-G1 angle value.
dihed1 : openff.units.Quantity
The H2-H1-H0-G0 dihedral value.
dihed2 : openff.units.Quantity
The H1-H0-G0-G1 dihedral value.
dihed3 : openff.units.Quantity
The H0-G0-G1-G2 dihedral value.
"""
bond = (
calc_bonds(
atomgroup.atoms[0].position,
atomgroup.atoms[3].position,
box=atomgroup.dimensions,
)
* unit.angstroms
)

angles = []
for idx_set in [[1, 0, 3], [0, 3, 4]]:
angle = calc_angles(
atomgroup.atoms[idx_set[0]].position,
atomgroup.atoms[idx_set[1]].position,
atomgroup.atoms[idx_set[2]].position,
box=atomgroup.dimensions,
)
angles.append(angle * unit.radians)

dihedrals = []
for idx_set in [[2, 1, 0, 3], [1, 0, 3, 4], [0, 3, 4, 5]]:
dihed = calc_dihedrals(
atomgroup.atoms[idx_set[0]].position,
atomgroup.atoms[idx_set[1]].position,
atomgroup.atoms[idx_set[2]].position,
atomgroup.atoms[idx_set[3]].position,
box=atomgroup.dimensions,
)
dihedrals.append(dihed * unit.radians)

return bond, angles[0], angles[1], dihedrals[0], dihedrals[1], dihedrals[2]


def find_boresch_restraint(
universe: mda.Universe,
guest_rdmol: Chem.Mol,
guest_idxs: list[int],
host_idxs: list[int],
guest_restraint_atoms_idxs: Optional[list[int]] = None,
host_restraint_atoms_idxs: Optional[list[int]] = None,
host_selection: str = "all",
dssp_filter: bool = False,
rmsf_cutoff: Quantity = 0.1 * unit.nanometer,
host_min_distance: Quantity = 1 * unit.nanometer,
host_max_distance: Quantity = 3 * unit.nanometer,
angle_force_constant: Quantity = (
83.68 * unit.kilojoule_per_mole / unit.radians**2
),
temperature: Quantity = 298.15 * unit.kelvin,
) -> BoreschRestraintGeometry:
"""
Find suitable Boresch-style restraints between a host and guest entity
based on the approach of Baumann et al. [1] with some modifications.

Parameters
----------
universe : mda.Universe
An MDAnalysis Universe defining the system and its coordinates.
guest_rdmol : Chem.Mol
An RDKit Mol for the guest molecule.
guest_idxs : list[int]
Indices in the topology for the guest molecule.
host_idxs : list[int]
Indices in the topology for the host molecule.
guest_restraint_atoms_idxs : Optional[list[int]]
User selected indices of the guest molecule itself (i.e. indexed
starting a 0 for the guest molecule). This overrides the
restraint search and a restraint using these indices will
be returned. Must be defined alongside ``host_restraint_atoms_idxs``.
host_restraint_atoms_idxs : Optional[list[int]]
User selected indices of the host molecule itself (i.e. indexed
starting a 0 for the hosts molecule). This overrides the
restraint search and a restraint using these indices will
be returned. Must be defined alongside ``guest_restraint_atoms_idxs``.
host_selection : str
An MDAnalysis selection string to sub-select the host atoms.
dssp_filter : bool
Whether or not to filter the host atoms by their secondary structure.
rmsf_cutoff : openff.units.Quantity
The cutoff value for atom root mean square fluctuation. Atoms with RMSF
values above this cutoff will be disregarded.
Must be in units compatible with nanometer.
host_min_distance : openff.units.Quantity
The minimum distance between any host atom and the guest G0 atom.
Must be in units compatible with nanometer.
host_max_distance : openff.units.Quantity
The maximum distance between any host atom and the guest G0 atom.
Must be in units compatible with nanometer.
angle_force_constant : openff.units.Quantity
The force constant for the G1-G0-H0 and G0-H0-H1 angles. Must be
in units compatible with kilojoule / mole / radians ** 2.
temperature : openff.units.Quantity
The system temperature in units compatible with Kelvin.

Returns
-------
BoreschRestraintGeometry
An object defining the parameters of the Boresch-like restraint.

References
----------
[1] Baumann, Hannah M., et al. "Broadening the scope of binding free energy
calculations using a Separated Topologies approach." (2023).
"""
if (guest_restraint_atoms_idxs is not None) and (host_restraint_atoms_idxs is not None): # fmt: skip
# In this case assume the picked atoms were intentional /
# representative of the input and go with it
guest_ag = universe.atoms[guest_idxs]
guest_atoms = [at.ix for at in guest_ag.atoms[guest_restraint_atoms_idxs]]
host_ag = universe.atoms[host_idxs]
host_atoms = [at.ix for at in host_ag.atoms[host_restraint_atoms_idxs]]

# Set the equilibrium values as those of the final frame
universe.trajectory[-1]
atomgroup = universe.atoms[host_atoms + guest_atoms]
bond, ang1, ang2, dih1, dih2, dih3 = _get_restraint_distances(atomgroup)

# TODO: add checks to warn if this is a badly picked
# set of atoms.
return BoreschRestraintGeometry(
host_atoms=host_atoms,
guest_atoms=guest_atoms,
r_aA0=bond,
theta_A0=ang1,
theta_B0=ang2,
phi_A0=dih1,
phi_B0=dih2,
phi_C0=dih3,
)

if (guest_restraint_atoms_idxs is not None) ^ (host_restraint_atoms_idxs is not None): # fmt: skip
# This is not an intended outcome, crash out here
errmsg = (
"both ``guest_restraints_atoms_idxs`` and "
"``host_restraint_atoms_idxs`` "
"must be set or both must be None. "
f"Got {guest_restraint_atoms_idxs} and {host_restraint_atoms_idxs}"
)
raise ValueError(errmsg)

# 1. Fetch the guest anchors
guest_anchors = find_guest_atom_candidates(
universe=universe,
rdmol=guest_rdmol,
guest_idxs=guest_idxs,
rmsf_cutoff=rmsf_cutoff,
)

if len(guest_anchors) == 0:
errmsg = "No suitable ligand atoms found for the restraint."
raise ValueError(errmsg)

# 2. We then loop through the guest anchors to find suitable host atoms
for guest_anchor in guest_anchors:
# We next fetch the host atom pool
# Note: return is a set, so need to convert it later on
host_pool = find_host_atom_candidates(
universe=universe,
host_idxs=host_idxs,
l1_idx=guest_anchor[0],
host_selection=host_selection,
dssp_filter=dssp_filter,
rmsf_cutoff=rmsf_cutoff,
min_distance=host_min_distance,
max_distance=host_max_distance,
)

host_anchor = find_host_anchor(
guest_atoms=universe.atoms[list(guest_anchor)],
host_atom_pool=universe.atoms[list(host_pool)],
minimum_distance=0.5 * unit.nanometer,
angle_force_constant=angle_force_constant,
temperature=temperature,
)
# continue if it's empty, otherwise stop
if host_anchor is not None:
break

if host_anchor is None:
errmsg = "No suitable host atoms could be found"
raise ValueError(errmsg)

# Set the equilibrium values as those of the final frame
universe.trajectory[-1]
atomgroup = universe.atoms[list(host_anchor) + list(guest_anchor)]
bond, ang1, ang2, dih1, dih2, dih3 = _get_restraint_distances(atomgroup)

return BoreschRestraintGeometry(
host_atoms=list(host_anchor),
guest_atoms=list(guest_anchor),
r_aA0=bond,
theta_A0=ang1,
theta_B0=ang2,
phi_A0=dih1,
phi_B0=dih2,
phi_C0=dih3,
)
Loading