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)