Source code for schrodinger.application.desmond.fep_schedule

"""This module handles the allocation of lambda windows for FEP simulations.

Different schemes of FEP simulations and interactions require different
schedules of lambda windows. The FepSchedule class abstracts the creation of
these schedules. This class is meant to be instantiated via its derived
classes (with mixins) or, preferably, using the get_fep_schedule function.

Copyright Schrodinger, LLC. All rights reserved.

"""

from typing import List
from typing import Optional
from typing import Tuple
from typing import Type

from schrodinger.application.desmond import constants


[docs]class FepSchedule: """Lambda schedule for FEP calculations. This class should not be instantiated directly. Instead, subclass it combining the mixins below. Attributes ---------- n_win : int Number of lambda windows charge_div : float or None Determines the fraction of lambda windows devoted to turning on electrostatic interactions. This should be overriden by a mix-in class or by the user. bonded_div : float or None Determines the fraction of lambda windows devoted to turning on bonded interactions. This should be overriden by a mix-in class or by the user. """ n_win: Optional[int] = None charge_div: Optional[float] = None bonded_div: Optional[float] = None # The following three attributes are mean to hold the actual schedules. _vdw: Optional[List[float]] = None _coulomb: Optional[List[float]] = None _bonded: Optional[List[float]] = None
[docs] def __init__(self, n_win: int, charge_div: Optional[float] = None) -> None: if n_win <= 0: raise ValueError(f'Invalid number of lambda windows: {n_win}') assert self.charge_div is not None, \ (f'Invalid charge_div. The class f{self.__class__.__name__!r} should ' 'not be instantiated directly') assert self.bonded_div is not None, \ (f'Invalid bonded_div. The class f{self.__class__.__name__!r} should ' 'not be instantiated directly') self.n_win = n_win if charge_div is not None: self.charge_div = charge_div n_coulomb = max(int(self.n_win / self.charge_div), 1) + 1 n_vdw = self.n_win - n_coulomb + 1 _coulomb = [0.0] * (self.n_win - n_coulomb) coulomb = self._calc_charge_lambda(n_coulomb) self._coulomb = _coulomb + coulomb vdw_ = [1.0] * (self.n_win - n_vdw + 1) vdw = self._calc_vdw_lambda(n_vdw) self._vdw = vdw + vdw_ n_bonded = self.n_win - int(self.n_win / self.bonded_div) bonded_ = [1.0] * (n_win - n_bonded - 1) bonded = self._calc_bonded_lambda(n_bonded) self._bonded = bonded + bonded_
@staticmethod def _calc_vdw_lambda(n: int) -> List[float]: """Return lambda values for vdW potentials. """ def cubic_poly(x: float) -> float: return 0.13418 * x - 0.03096 * x * x + 0.0037542 * x * x * x return [cubic_poly(k * 8.0 / (n - 1)) for k in range(n - 1)] @staticmethod def _calc_charge_lambda(n: int) -> List[float]: """Return lambda values for Coulomb potentials. """ return [i / (n - 1) for i in range(n)] @staticmethod def _calc_bonded_lambda(n: int) -> List[float]: """Return lambda values for bonded potentials. """ return [i / (n - 1) for i in range(n)]
[docs] def get_lambda(self): return self # For backwards-compatibility.
[docs]class FepScheduleAlchemical(FepSchedule): @property def vdw(self): return [1.0] * self.n_win @property def coulomb(self): return [0.0] * self.n_win @property def vdwA(self): return list(reversed(self._vdw)) @property def vdwB(self): return self._vdw @property def chargeA(self): return list(reversed(self._coulomb)) @property def chargeB(self): return self._coulomb @property def bondedA(self): return list(reversed(self._bonded)) @property def bondedB(self): return self._bonded
[docs]class FepScheduleBinding(FepSchedule): @property def vdw(self): return self._vdw @property def coulomb(self): return self._coulomb @property def vdwA(self): return [1.0] * self.n_win @property def vdwB(self): return [1.0] * self.n_win @property def chargeA(self): return [0.0] * self.n_win @property def chargeB(self): return [0.0] * self.n_win @property def bondedA(self): return [1.0] * self.n_win @property def bondedB(self): return [1.0] * self.n_win
[docs]class FepSchemeMixIn: """Configure schedule of FEP schedule. """ charge_div = 5 / 2 bonded_div = float('inf')
[docs]class FepSchemeChargeMixIn(FepSchemeMixIn): charge_div = 3 / 2 @staticmethod def _calc_charge_lambda(n) -> List[float]: """Return a list of lambda values for Coulomb potentials. Note that the computed schedule is quadratic. """ return [1.0 - (i / (n - 1) - 1.0)**2 for i in range(n)]
[docs]class FepSchemeQuickchargeMixIn(FepSchemeMixIn): charge_div = 5.0
[docs]class FepSchemeSuperquickchargeMixIn(FepSchemeMixIn): charge_div = 10.0 bonded_div = 10.0
[docs]def get_fep_schedule_class(fep_type: str = 'alchemical', scheme: str = 'default') -> Type[FepSchedule]: """Return FEP schedule class. See also -------- get_fep_schedule """ base_classes = { 'alchemical': FepScheduleAlchemical, 'binding': FepScheduleBinding } mixin_classes = { 'default': FepSchemeMixIn, 'flexible': FepSchemeMixIn, 'charge': FepSchemeChargeMixIn, 'quickcharge': FepSchemeQuickchargeMixIn, 'superquickcharge': FepSchemeSuperquickchargeMixIn } class Schedule(mixin_classes[scheme], base_classes[fep_type]): pass return Schedule
[docs]def get_fep_schedule(n_win: int, fep_type: str = 'alchemical', scheme: str = 'default') -> FepSchedule: """Instantiate FEP schedule. Return a class encapsulating a concrete lambda schedule for the specified FEP simulation type and scheme. :param n_win: Number of lambda windows. :type n_win: int :param fep_type: Type of FEP simulation. Can be either 'alchemical' (default) or 'binding'. :type fep_type: str :param scheme: Simulation scheme. Can be one of 'default', 'flexible', 'charge', 'quickcharge', or 'superquickcharge'. :type scheme: str :return: Concrete schedule of the specified type, scheme, and number of windows. :rtype: FepSchedule """ FepSchedule = get_fep_schedule_class(fep_type, scheme) return FepSchedule(n_win)
[docs]class FepScheduleAlchemicalDefault(FepSchemeMixIn, FepScheduleAlchemical): """Convenience class for the alchemical schedule with the default scheme. """
[docs]class FepScheduleAlchemicalCharge(FepSchemeChargeMixIn, FepScheduleAlchemical): """Convenience class for the alchemical schedule with the charge scheme. """
[docs]class FepScheduleBindingDefault(FepSchemeMixIn, FepScheduleBinding): """Convenience class for the binding schedule with the default scheme. """
[docs]def get_absolute_binding_num_lambda_windows( protocol: constants.SIMULATION_PROTOCOL, restrained: bool) -> Tuple[int, int]: """ Get the number of lambda windows in the absolute binding lambda schedules. These are derived from the lambda schedules in mmshare/data/desmond/abfep :return: A tuple of # of complex windows, # of solvent windows """ if restrained: if protocol == constants.SIMULATION_PROTOCOL.CHARGED: return 128, 68 else: return 80, 68 else: if protocol == constants.SIMULATION_PROTOCOL.CHARGED: return 108, 60 else: return 68, 60