Source code for schrodinger.ui.qt.forcefield.forcefield
"""
Contains Force Field selector widgets
"""
import enum
from contextlib import contextmanager
from typing import Optional
import schrodinger
from schrodinger.forcefield import OPLS_DIR_ENV
from schrodinger.forcefield.custom_params import upgrade_oplsdir
from schrodinger.infra import mm
from schrodinger.infra.mm import \
get_preference_use_custom_opls as get_use_custom_forcefield_preference
from schrodinger.Qt import QtWidgets
from schrodinger.ui import maestro_ui
from schrodinger.ui.qt import messagebox
from schrodinger.utils.env import EnvironmentContext
maestro = schrodinger.get_maestro()
maestro_hub = maestro_ui.MaestroHub.instance()
MIGRATED_MESSAGE = (
'Your custom force field parameters have been automatically migrated to'
' the customizations file associated with the current release.\n\n'
'Note that any new customizations you define will only be available with'
' this version of the Schrodinger Suite, as older versions of Maestro will'
' continue to use the older parameter files.')
SAVE_RESPONSE_KEY = 'use_default_ff'
PROJECT_PREFER_COMMAND = 'projectprefer'
PROJECT_OPLS_DIR_CMD_OPTION = 'projectoplsdirectory'
_no_maestro_project_opls_path = ''
[docs]def get_custom_opls_dir():
"""
Returns current project opls path if present, else
returns global preference custom opls path.
:return: path to the OPLS directory.
no validation occurs for returned directories.
:rtype: str
"""
return get_project_preference_custom_opls_path(
) or mm.get_preference_custom_opls_path()
F14_DISP_NAME = mm.opls_version_to_name(mm.OPLSVersion.F14, mm.OPLSName_DISPLAY)
F16_DISP_NAME = mm.opls_version_to_name(mm.OPLSVersion.F16, mm.OPLSName_DISPLAY)
FFS_INCOMPATIBLE_PARAMS_MSG = f"""<html><p>Your customizations
<a style="text-decoration:none;color:#3D3DF9;" href="opls_path">(path)</a>
are either unavailable or incompatible with the {F16_DISP_NAME} force
field currently in use in Schrödinger suite. New custom parameters are
needed to accurately model your structures.</p>
<p>You may run this job without customizations, but we recommend
that you cancel now and generate the new parameters. Use the Force
Field Builder, available from a Customize button on your panel or
from the Task Tool.</p>"""
[docs]def has_valid_custom_OPLS_preference():
"""
:return: whether the custom OPLS directory is a valid S-OPLS path
:rtype: bool
"""
opls_dir = get_custom_opls_dir()
return opls_dir is None or mm.is_valid_opls_directory(opls_dir)
[docs]class OPLSDirResult(enum.IntEnum):
"""
Return object for validate_opls_dir function
"""
ABORT = 0 # Cancel
VALID = 1 # opls_dir is fine, use it
DEFAULT = 2 # opls_dir is incompatible, use the default opls dir
[docs]class OPLSValidator:
"""
Validates OPLS-related settings. Meant for use by GUIs that may need to
block other code while performing validation.
"""
INCOMPATIBLE_PARAMS_MSG = FFS_INCOMPATIBLE_PARAMS_MSG
[docs] def __init__(self, parent: QtWidgets.QWidget):
"""
:ivar parent: The parent object from which to display messageboxes
:vartype parent: QWidget
"""
self.parent = parent
self._validating = False
@property
def validating(self) -> bool:
"""
Return whether or not the validator is currently performing validation.
"""
return self._validating
[docs] def validateOPLSDir(self,
opls_dir: Optional[str] = None,
allow_default_dialog: bool = True) -> OPLSDirResult:
"""
Return validation results of OPLS_DIR. If invalid, attempt to update the
OPLS_DIR. If invalid and can't update OPLS_DIR, optionally prompt
`ValidateOplsDirMessageBox`. See validate_opls_dir() for full
documentation.
:param opls_dir: Path to the custom OPLS directory. Use to avoid calling
get_custom_opls_dir() twice in the event that the function is
already called outside of this method. If not given or `None` the
validation will be done for the user's preferences custom OPLS
directory.
:param allow_default_dialog: whether the user may be presented with the
dialog allowing them to run with the default OPLS dir.
"""
if opls_dir is None:
opls_dir = get_custom_opls_dir()
self._validating = True
try:
valid = validate_opls_dir(
opls_dir,
parent=self.parent,
incompatible_params_msg=self.INCOMPATIBLE_PARAMS_MSG,
allow_default_dialog=allow_default_dialog)
finally:
self._validating = False
return valid
[docs]@contextmanager
def use_custom_opls_dir_preference():
"""
If specified, sets the custom OPLSDIR Maestro preference into the env in a
context managed scope.
"""
if get_use_custom_forcefield_preference():
with EnvironmentContext(OPLS_DIR_ENV, get_custom_opls_dir()):
yield
else: # do not set OPLS_DIR_ENV
yield
[docs]class ValidateOplsDirMessageBox(messagebox.MessageBox):
"""
A message box allowing the user to cancel when the custom opls dir is not
valid, or run with the default opls dir.
A check box exists to allow unset the Maestro preference for using the
custom opls dir.
:cvar title: dialog window title
:vartype title: str
:cvar text: the message text
:vartype text: str
:ivar cancel_btn: the cancel button
:vartype cancel_btn: QtWidgets.QPushButton
:ivar run_defaults_btn: the 'Run with Defaults' button
:vartype run_defaults_btn: QtWidgets.QPushButton
:ivar incompatible_params_msg: the message to display when incompatible
parameters are found.
:vartype incompatible_params_msg: str
"""
TITLE = "Custom Parameters Incompatible"
CB_TEXT = 'Do not show this message again'
[docs] def __init__(
self,
opls_dir,
parent=None,
incompatible_params_msg=FFS_INCOMPATIBLE_PARAMS_MSG,
):
super().__init__(parent=parent,
save_response_key=SAVE_RESPONSE_KEY,
add_prefix_to_key=False)
# Set add_prefix_to_key to False to ensure that the "Do not show"
# state is shared between all force field selector subclasses.
self.save_response_chk.setText(self.CB_TEXT)
self.opls_dir = opls_dir
self.setIcon(QtWidgets.QMessageBox.Question)
self.setWindowTitle(self.TITLE)
self._incompatible_params_msg = incompatible_params_msg
self.setText(self._incompatible_params_msg)
# find the QLabel that has our text and hook up the link call back
for label in (lbl for lbl in self.children()
if isinstance(lbl, QtWidgets.QLabel)):
if label.text() == self._incompatible_params_msg:
break
label.setOpenExternalLinks(False)
label.linkActivated.connect(self._showPath)
label.linkHovered.connect(self._showPath)
self.run_defaults_btn = self.addButton('Run with Defaults',
QtWidgets.QMessageBox.AcceptRole)
cancel_btn = self.addButton('Cancel', QtWidgets.QMessageBox.RejectRole)
self.setDefaultButton(cancel_btn)
self.setEscapeButton(cancel_btn)
self.layout().setVerticalSpacing(20)
self.run_defaults_btn.setMinimumWidth(100)
def _showPath(self):
"""
Callback method to show the tool tip for the link in the text
"""
QtWidgets.QToolTip.showText(self.cursor().pos(), self.opls_dir)
[docs] def getResponse(self):
if self.clickedButton() == self.run_defaults_btn:
return True
return None
[docs]def validate_opls_dir(opls_dir,
parent=None,
incompatible_params_msg=FFS_INCOMPATIBLE_PARAMS_MSG,
allow_default_dialog=True):
"""
Determine whether the automatically upgraded OPLS_DIR is valid.
Display a message box to inform the user whether an automatic upgrade was
performed.
If the OPLS dir remains invalid and `allow_default_dialog` is True a dialog
allowing the user to use the default OPLS dir is presented.
:param opls_dir: the opls directory path or None
:type opls_dir: str or None
:param parent: the QtWidget used to post dialogs
:type parent: QtWidgets.QtWidget
:param incompatible_params_msg: the message to display when incompatible
parameters are found. Default is for `ValidateOplsDirMessageBox` to use
FFS_INCOMPATIBLE_PARAMS_MSG.
:type incompatible_params_msg: str
:param allow_default_dialog: whether the user may be presented with the
`ValidateOplsDirMessageBox` allowing them to run with the default OPLS
dir.
:type allow_default_dialog: bool
:return: the validation result
:rtype: OPLSDirResult
"""
if opls_dir is None or mm.is_valid_opls_directory(opls_dir):
return OPLSDirResult.VALID
try:
upgrade_oplsdir(opls_dir)
except RuntimeError:
pass
else:
messagebox.show_info(parent, MIGRATED_MESSAGE)
return OPLSDirResult.VALID
if not allow_default_dialog:
return OPLSDirResult.ABORT
msg_box = ValidateOplsDirMessageBox(opls_dir, parent,
incompatible_params_msg)
response = msg_box.exec()
if response:
return OPLSDirResult.DEFAULT
return OPLSDirResult.ABORT
[docs]def get_project_preference_custom_opls_path():
"""
Returns project specific opls directory.
:return: path to custom OPLS parameters stored in the project-specific
preferences
:rtype: str
"""
if not maestro:
return _no_maestro_project_opls_path
return maestro.get_command_option(PROJECT_PREFER_COMMAND,
PROJECT_OPLS_DIR_CMD_OPTION)
[docs]def set_project_preference_custom_opls_path(project_opls_path):
"""
Sets project specific opls custom paramteres path.
:param project_opls_path: current project oplsdir path
:type project_opls_path: str
"""
if not maestro:
global _no_maestro_project_opls_path
_no_maestro_project_opls_path = project_opls_path
return
cmd = f'{PROJECT_PREFER_COMMAND} {PROJECT_OPLS_DIR_CMD_OPTION}="{project_opls_path}"'
maestro.command(cmd)