"""
Phase driver for the Develop Common Pharmacophore Hypotheses (DHCP) workflow.
Copyright Schrodinger LLC, All Rights Reserved.
"""
import os
import subprocess
from schrodinger.application.phase import input as phase_input
from schrodinger.application.phase import hypothesis
from schrodinger.infra import phase
from schrodinger.utils import fileutils
from schrodinger.utils import log
# Logging
logger = log.get_output_logger(__file__)
# Schrodinger env variable
SCHRODINGER_UTIL = os.path.join(os.getenv("SCHRODINGER"), "utilities")
XVOL_SHELL_DRIVER = os.path.join(SCHRODINGER_UTIL, "create_xvolShell")
XVOL_CLASH_DRIVER = os.path.join(SCHRODINGER_UTIL, "create_xvolClash")
XVOL_RECEPTOR_DRIVER = os.path.join(SCHRODINGER_UTIL, "create_xvolReceptor")
[docs]class ExcludedVolumeGenerator(object):
"""
Class to create excluded volumes for a given PhaseHypothesis.
"""
[docs] def __init__(self, base_hypothesis):
"""
Initialize by creating a temp copy of the base hypothesis.
:param base_hypothesis:
:type base_hypothesis: str or `PhpHypoAdaptor`
"""
self.base_hypo = base_hypothesis
def _getSettings(self, settings_inputconfig):
"""
Initialize the class, updating the PhaseHypothesisInputConfig mode,
validating the settings, and creating a member namedtuple.
:param settings_inputconfig: Phase derived InputConfig instance
:type settings_inputconfig: `PhaseHypothesisInputConfig`
"""
if not settings_inputconfig:
return None
#settings_inputconfig.setInputMode(phase_input.InputMode.create_xvol)
settings_inputconfig.validateInput()
return settings_inputconfig.asNamedTuple()
[docs] def createExcludedVolumeShell(self, active_sts, settings):
"""
Runs the excluded volume shell creation for given active structures.
create_xvolShell -hypo <hypo> -ref <actives> [options]
:param active_sts: structures to build excluded volume shell around
:type active_sts: list of `structure.Structure`
:param settings: calculation settings
:type settings: `PhaseHypothesisInputConfig`
:return: Excluded volume based on active structure shell
:rtype: `phase.PhpExclVol`
"""
xvol_settings = self._getSettings(settings)
if not xvol_settings.excluded_volume_mode == "shell":
raise RuntimeError("Unexpected excluded volume mode.")
args = []
if xvol_settings.min_surface_to_volume_distance:
buff_dist = xvol_settings.min_surface_to_volume_distance
args.extend(["-buff", buff_dist])
if xvol_settings.excluded_volume_sphere_radii:
args.extend(["-grid", xvol_settings.excluded_volume_sphere_radii])
if xvol_settings.append_excluded_volumes:
args.append("-append")
# If using a reference structure, create and run with TempStructureFile
if active_sts:
with fileutils.TempStructureFile(active_sts) as ref_filename:
args = ["-ref", ref_filename] + args
xvol = self._runCreateXvolCommand(XVOL_SHELL_DRIVER, args)
# Otherwise, run the driver with no structure file
else:
xvol = self._runCreateXvolCommand(XVOL_SHELL_DRIVER, args)
return xvol
[docs] def createExcludedVolumeClash(self, active_sts, inactive_sts, settings):
"""
Runs the excluded volume clash creation for a given set of active and
inactive structures.
create_xvolClash -hypo <hypo> -pos <actives> -neg <inactives> [options]
:param active_sts: active structures
:type active_sts: list of `structure.Structure`
:param inactive_sts: inactive structures
:type inactive_sts: list of `structure.Structure`
:param settings: calculation settings
:type settings: `PhaseHypothesisInputConfig`
:return: Excluded volume based on active/inactive clash
:rtype: `phase.PhpExclVol`
"""
xvol_settings = self._getSettings(settings)
if not xvol_settings.excluded_volume_mode == "clash":
raise RuntimeError("Unexpected excluded volume mode.")
args = []
if xvol_settings.min_surface_to_volume_distance:
buff_dist = xvol_settings.min_surface_to_volume_distance
args.extend(["-buff", buff_dist])
if xvol_settings.min_num_inactives_with_clash:
args.extend(["-freq", xvol_settings.min_num_inactives_with_clash])
if xvol_settings.excluded_volume_sphere_radii:
args.extend(["-grid", xvol_settings.excluded_volume_sphere_radii])
if xvol_settings.append_excluded_volumes:
args.append("-append")
with fileutils.TempStructureFile(active_sts) as actives_file, \
fileutils.TempStructureFile(inactive_sts) as inactives_file:
args = ["-pos", actives_file, "-neg", inactives_file] + args
xvol = self._runCreateXvolCommand(XVOL_CLASH_DRIVER, args)
return xvol
[docs] def createExcludedVolumeReceptor(self, receptor_st, settings):
"""
Runs the excluded volume creation for a given receptor
create_xvolReceptor -hypo <hypo> -receptor <receptor> [options]
:param receptor_st: receptor structure
:type receptor_st: `structure.Structure`
:param settings: calculation settings
:type settings: `PhaseHypothesisInputConfig`
:return: Excluded volume based on active/inactive clash
:rtype: `phase.PhpExclVol`
"""
xvol_settings = self._getSettings(settings)
if not xvol_settings.excluded_volume_mode == "receptor":
raise RuntimeError("Unexpected excluded volume mode.")
args = []
if xvol_settings.min_surface_to_volume_distance:
buff_dist = xvol_settings.min_surface_to_volume_distance
args.extend(["-buff", buff_dist])
if xvol_settings.receptor_radii_size_value:
args.extend(["-radius", xvol_settings.receptor_radii_size_value])
if xvol_settings.receptor_radii_size_prop:
args.extend(["-rprop", xvol_settings.receptor_radii_size_prop])
if xvol_settings.receptor_radii_scaling_value:
radii_scale = xvol_settings.receptor_radii_scaling_value
args.extend(["-scale", radii_scale])
if xvol_settings.receptor_radii_scaling_prop:
args.extend(["-sprop", xvol_settings.receptor_radii_scaling_prop])
if xvol_settings.receptor_shell_limit:
args.extend(["-limit", xvol_settings.receptor_shell_limit])
if xvol_settings.append_excluded_volumes:
args.append("-append")
with fileutils.TempStructureFile([receptor_st]) as receptor_file:
args = ["-receptor", receptor_file] + args
xvol = self._runCreateXvolCommand(XVOL_RECEPTOR_DRIVER, args)
return xvol
def _runCreateXvolCommand(self, xvol_driver, args):
"""
Build and run the excluded volume driver. All excluded volume
calculations rquire a hypothesis name to work from.
:param xvol_driver: excluded volume driver name
:type xvol_driver: str
:param args: list of command arguments
:type args: list
:return: a new excluded volume based on a given hypothesis
:rtype: `phase.PhpExclVol`
"""
# Create temp hypothesis file for the backend
with fileutils.tempfilename(suffix=".phypo") as temp_hypo:
# Build command
phase.PhpHypoAdaptor(self.base_hypo).save(temp_hypo, True)
cmd = [xvol_driver, "-hypo", fileutils.splitext(temp_hypo)[0]]
cmd.extend([str(a) for a in args])
# Run the utilitiy
try:
subprocess.check_output(cmd, universal_newlines=True)
except subprocess.CalledProcessError as e:
logger.error("Command:\n" + " ".join(e.cmd))
logger.error("Output:\n" + e.output)
raise RuntimeError("create_xvol command has failed.")
hypo = hypothesis.PhaseHypothesis(temp_hypo)
return hypo.getXvol()