Source code for schrodinger.application.jaguar.gui.tabs.base_tab

import warnings

from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets

from ..utils import THEORY_DFT
from ..utils import THEORY_HF
from ..utils import THEORY_LMP2
from ..utils import THEORY_RIMP2
from ..utils import JaguarSettingWarning


[docs]class CombinedUi(object): """ An object that provides access to widgets across multiple ui files """
# This class intentionally left blank. Instances of object() cannot have # arbitrary attributes set, but instances of subclasses of object can.
[docs]class BaseTab(QtWidgets.QWidget): """ The base class for all Jaguar tabs. Subclasses should define NAME, HELP_TOPIC, UI_MODULES, setup(), getMmJagKeywords(), restoreSettings(), and reset(). :cvar NAME: The name of the tab :vartype NAME: str :cvar HELP_TOPIC: The help topic for the tab :vartype HELP_TOPIC: str :cvar UI_MODULES: A tuple of ui modules defining the tab widgets. These ui modules should be listed top to bottom. Optionally, a tuple of (tab class, attribute name) may be listed instead of a ui file to include a subtab. :vartype UI_MODULES: tuple :ivar ui: An object that provides access to all widgets from all ui files. Note that widget names should be unique amongst all ui files for the tab. :vartype ui: CombinedUi :ivar task_name: The name of the panel this tab is part of :vartype task_name: str :ivar input_selector: The AppFramework input selector frame from the panel containing this tab. :vartype input_selector: schrodinger.ui.qt.input_selector.InputSelector """ NAME = "" HELP_TOPIC = "" # Help topic names are given in maestro/src/jaguar/mm_vjaguar.cxx # setJaguarHelpID() (line 412) UI_MODULES = None
[docs] def __init__(self, parent, input_selector=None): """ Perform common tab initialization. All tab specific initialization should go in setup(). :param parent: The Qt parent widget :type parent: PyQt5.QtWidgets.QWidget :param input_selector: The panel's input selector widget, if available. :type input_selector: schrodinger.ui.qt.input_selector.InputSelector """ super().__init__(parent) self.ui = CombinedUi() self._populateUi() self.input_selector = input_selector self.setup()
def _populateUi(self): """ Instantiate all ui files and subtabs and add them to a single layout. All widgets will be added to self.ui. """ main_layout = QtWidgets.QVBoxLayout(self) for cur_ui in self.UI_MODULES: if isinstance(cur_ui, tuple): tab_class, name = cur_ui cur_widget = tab_class(self) setattr(self.ui, name, cur_widget) else: cur_widget = QtWidgets.QWidget(self) cur_form = cur_ui.Ui_Form() cur_form.setupUi(cur_widget) self.ui.__dict__.update(cur_form.__dict__) cur_widget.layout().setContentsMargins(0, 0, 0, 0) main_layout.addWidget(cur_widget) main_layout.addStretch()
[docs] def setup(self): """ Perform tab specific initialization. This function should be defined in subclasses if initialization is needed. """
[docs] def getDefaultKeywords(self): """ Get the default keywords for this tab. Note that defaults that exist in mmjag should not be explicitly set here. """ return {}
[docs] def getMmJagKeywords(self): """ Return all keywords that should be put into the mmjag handle. This function should be defined in subclasses. :return: All keywords that should be put into the mmjag handle :rtype: dict :raise schrodinger.application.jaguar.gui.utils.JaguarSettingError: If any settings are invalid. """ return {}
[docs] def loadSettings(self, jag_input): """ Restore tab settings from mmjag keywords. This function should be defined in subclasses. :param jag_input: The Jaguar settings to base the tab settings on :type jag_input: schrodinger.application.jaguar.input.JaguarInput """
[docs] def loadPerAtomSettings(self, jag_input, eid=None, title=None): """ Restore per-atom tab settings from mmjag keywords. This function should be defined in subclasses for any tabs that contain per-atom settings. :param jag_input: The Jaguar settings to base the tab settings on :type jag_input: schrodinger.application.jaguar.input.JaguarInput :param eid: The entry id of the structure in `jag_input` :type eid: str :param title: The title of the structure in `jag_input` :type title: str """
[docs] def saveSettings(self, jag_input, eid): """ Save tab settings in jaguar handle. It is only used for settings which are not defined using keywords (typically per-atom settings). This function should be defined in subclasses for any tabs that contain per- atom settings. :param jag_input: The Jaguar handle to store the tab settings in :type jag_input: schrodinger.application.jaguar.input.JaguarInput :param eid: The entry id of the structure in `jag_input` :type eid: str """
[docs] def reset(self): """ Reset the tab to its original state. Note that this function is only necessary for settings that are not stored in the mmjag handle. For mmjag settings, reset will be carried out using `loadSettings` with a default `schrodinger.application.jaguar.input.JaguarInput` object. This function should only be defined in subclasses if there are any settings that cannot be reset via `loadSettings`. """
[docs] def validate(self): """ Make sure that the tab settings will allow a job to be run successfully. Note that this validation should not be redundant with that performed in `getMmJagKeywords`. Any tab setting that prevents valid mmjag keywords from being generated should cause `getMmJagKeywords` to raise a JaguarSettingError. Any tab setting that allows mmjag keywords to be successfully generated but will result in a job failure should cause a `validate` fail. This function should be defined in subclasses if validation is needed. :return: If the validation passes, None is returned. If the validation fails, a string that describes the error is returned. :rtype: str or NoneType """
[docs] def error(self, msg): """ Display an error dialog with the specified message :param msg: The message to include in the error dialog :type msg: str """ QtWidgets.QMessageBox.critical(self, "Error", msg)
[docs] def warning(self, msg): """ Display a warning dialog with the specified message :param msg: The message to include in the warning dialog :type msg: str """ QtWidgets.QMessageBox.warning(self, "Warning", msg)
[docs] def activate(self): """ This function will be called whenever the user selects this tab. Subclasses can override it if necessary to, e.g., activate picking. """
[docs] def deactivate(self): """ This function will be called whenever the user selects a different tab. Subclasses can override it if necessary to, e.g., deactivate picking. """
[docs]class ProvidesBasisMixin(object): """ A mixin for tabs that allow the user to select a basis set. This mixin is used to find tabs (via basePanel.getTab()) that can provide basis information. :cvar basis_changed: A signal emitted when the basis set is changed :vartype basis_changed: PyQt5.QtCore.pyqtSignal """ basis_changed = QtCore.pyqtSignal()
[docs] def getBasis(self, mixed_name="Mixed"): """ Get the currently selected basis. If more than one basis set is specified, `mixed_name` will be returned. If the tab does not allow for specifying multiple basis sets, then this argument may be ignored. :param mixed_name: The name to return if more than one basis set is specified :type mixed_name: str :return: The currently selected basis :rtype: str """ raise NotImplementedError
[docs]class LoadsTheoryMixin: """ A mixin for tabs that load theory settings from an input file. """
[docs] def getTheoryLevel(self): """ Get the current theory level :return: The current theory level. Will be one of: "DFT", "HF", "LMP2", "RIMP2" or "Mixed" :rtype: str """ raise NotImplementedError
[docs] def getAllUsedTheoryLevels(self): """ :return: A tuple of all theory levels used :rtype: tuple """ raise NotImplementedError
def _determineTheoryLevel(self, jag_input): """ Determine the level of theory specified by given settings. If a non- default LMP2 level is specified, a warning will be issued. :param jag_input: A JaguarInput object containing the settings to check :type jag_input: schrodinger.application.jaguar.input.JaguarInput :return: The level of theory. Will be one of "DFT", "HF", "LMP2", "RIMP2". :rtype: str """ mp2_level = jag_input[mm.MMJAG_IKEY_MP2] rimp2_level = jag_input[mm.MMJAG_IKEY_RIMP2] if mp2_level: if mp2_level != mm.MMJAG_MP2_VALENCE2: msg = ( f"Non-default LMP2 level {mm.MMJAG_IKEY_MP2}={mp2_level} not available in GUI. " f"Default {mm.MMJAG_IKEY_MP2}={mm.MMJAG_MP2_VALENCE2} will be used instead." ) warnings.warn(JaguarSettingWarning(msg)) return THEORY_LMP2 elif jag_input[mm.MMJAG_SKEY_DFTNAME] or jag_input[mm.MMJAG_IKEY_IDFT]: return THEORY_DFT elif rimp2_level: return THEORY_RIMP2 else: return THEORY_HF
[docs] def getMethod(self): """ Get the current method. If the level of theory is DFT, the method is the functional (ex. "B3LYP" or "PWB6K"). For all other levels of theory, the method is the level of theory ("HF" or "LMP2" or "RIMP2"). :return: The current method :rtype: str """ theory_level = self.getTheoryLevel() if theory_level == THEORY_DFT: return self._getDftFunctional() else: return theory_level
[docs] def getFunctional(self): """ Get the current DFT functional (ex. "B3LYP" or "PWB6K"). If the theory level is not DFT, then None will be returned. :return: The current DFT functional or None :rtype: str or NoneType """ if self.getTheoryLevel() == THEORY_DFT: return self._getDftFunctional() else: return None
def _getDftFunctional(self): """ Get the current DFT functional. This function should only be used when the theory level is DFT. :return: The current DFT functional :rtype: str """ raise NotImplementedError def _loadAndResetFunctional(self, jag_input, theory_level): """ If the theory level is DFT, load the specified functional from the mmjag handle. If the theory level isn't DFT, reset the DFT functional. :param jag_input: A JaguarInput object containing the settings to load :type jag_input: schrodinger.application.jaguar.input.JaguarInput :param theory_level: The level of theory to load the settings into. Must be either "DFT", "HF", or "LMP2". :type theory_level: str """ raise NotImplementedError def _checkSettings(self, jag_input): """ Sanity check the specified mmjag settings and make sure that we can uniquely determine a level of theory. :param jag_input: A JaguarInput object containing the settings to check :type jag_input: schrodinger.application.jaguar.input.JaguarInput :return: None if the settings are acceptable. A string describing the error if the sanity check fails. :rtype: NoneType or str """ if jag_input.sectionDefined(mm.MMJAG_SECT_LMP2): err = ("Cannot load theory settings from an input file with a " "&lmp2 section.") return err elif jag_input.sectionDefined(mm.MMJAG_SECT_GVB): err = ("Cannot load theory settings from an input file with a " "&gvb section.") return err try: non_default_keys = list(jag_input.getNonDefault()) except ValueError as err: # Note: Exception occurs when the user input type did not match the # expected type for the corresponding keyword. # Ex: nops_opt_switch = 'input' -> Value here should have been a # float, instead of a string. return str(err) lmp2_keys = self.LMP2_KEYWORDS.intersection(non_default_keys) dft_or_hf_keys = self.DFT_OR_HF_KEYWORDS.intersection(non_default_keys) dft_grid_keys = self.DFT_GRID_KEYWORDS.intersection(non_default_keys) dft_only_keys = self.DFT_ONLY_KEYWORDS.intersection(non_default_keys) if lmp2_keys and (dft_or_hf_keys or dft_grid_keys or dft_only_keys): dft_and_hf_keys = dft_or_hf_keys | dft_grid_keys | dft_only_keys err = ("Keywords for both LMP2 (%s) and DFT/HF (%s) found." % (", ".join(lmp2_keys), ", ".join(dft_and_hf_keys))) return err if dft_grid_keys and not dft_only_keys: plural_s = "s" if len(dft_grid_keys) > 1 else "" err = ("Grid density set (%s keyword%s present) but no DFT " "functional keyword found (%s). Grid density may not be " "set for levels of theory other than DFT." % (", ".join(dft_grid_keys), plural_s, mm.MMJAG_SKEY_DFTNAME)) return err if jag_input[mm.MMJAG_IKEY_MP2] and jag_input[mm.MMJAG_SKEY_RELHAM]: err = ("Cannot use ZORA (%s=%s) with LMP2 (%s=%s)." % (mm.MMJAG_SKEY_RELHAM, jag_input[mm.MMJAG_SKEY_RELHAM], mm.MMJAG_IKEY_MP2, jag_input[mm.MMJAG_IKEY_MP2])) return err # TODO: determine whether anything needs to be done for RIMP2 (PANEL-20565). def _loadFunctional(self, jag_input): """ Load the DFT functional setting from a JaguarInput object into the GUI. If no functional or an unknown functional is specified, a warning will be issued. :param jag_input: A JaguarInput object containing the settings to load :type jag_input: schrodinger.application.jaguar.input.JaguarInput """ functional = jag_input[mm.MMJAG_SKEY_DFTNAME] functional = functional.upper() if not functional: msg = "No DFT functional set (%s)." % mm.MMJAG_SKEY_DFTNAME warnings.warn(JaguarSettingWarning(msg)) else: try: self._setFunctional(functional) except ValueError: msg = ("Unrecognized DFT functional %s=%s." % (mm.MMJAG_SKEY_DFTNAME, functional)) warnings.warn(JaguarSettingWarning(msg))
[docs]class ProvidesTheoryMixin(LoadsTheoryMixin): """ A mixin for tabs that allow the user to select a theory level. This mixin is used to find tabs (via basePanel.getTab()) that can provide theory information. :cvar method_changed: A signal emitted when the theory level or DFT functional is changed :vartype method_changed: PyQt5.QtCore.pyqtSignal """ method_changed = QtCore.pyqtSignal() def _setFunctional(self, functional): """ Set the GUI to the specified DFT functional :param functional: The functional to set :type functional: str :raise ValueError: If the specified functional is not present in the GUI """ raise NotImplementedError
[docs] def getTheoryLevelForEid(self, eid): """ Return the theory level set for the specified entry ID. :param eid: Entry ID to get theory level for. :type eid: int :return: The theory level set for this entry ID. :rtype: str """ raise NotImplementedError
[docs] def getMethodForEid(self, eid): """ Return the method set for the specified entry ID. :param eid: Entry ID to get the method for :type eid: int :return: Method for this entry ID :rtype: str """ raise NotImplementedError
[docs] def getCommonTheoryLevel(self): """ :return: If all entries have the same theory level set, return the theory. level Otherwise return None :rtype: str or None """ raise NotImplementedError
[docs] def getCommonMethod(self): """ :return: If all entries have the same method set, return the method. Otherwise return None :rtype: str or None """ raise NotImplementedError
[docs] def getCommonFunctional(self): """ :return: If all entries use DFT and have the same functional set, return the functional. Otherwise return None. :rtype: str or None """ method = self.getCommonMethod() if method in (THEORY_HF, THEORY_LMP2, THEORY_RIMP2, None): return None else: return method
#PyQt5 only allows the signal to be # inherited if the base-class is a non-Qt class.
[docs]class ProvidesStructuresMixin(object): """ A mixin for tabs that allow the user to select structures. This mixin is used to find tabs (via basePanel.getTab()) that can provide structure information. """ structureChanged = QtCore.pyqtSignal() # The structureChanged signal gets added by the metaclass to work around a # limitation in PyQt's multiple inheritance abilities # structureChanged = QtCore.pyqtSignal() MULTIPLE_STRUC_JOB_TITLE = "batch"
[docs] def getStructureTitleForJobname(self): """ Get the structure title to be used in the job name. If the tab includes multiple structures, then `MULTIPLE_STRUC_JOB_TITLE` should be returned. If no structures have been specified yet, then None should be returned. :return: The structure title :rtype: str or NoneType """ raise NotImplementedError