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

import warnings

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

from .. import ui
from ..utils import JaguarSettingError
from ..utils import JaguarSettingWarning
from .molecule_tab import MoleculeTabNoInputSelector
from .multi_structure_tab import MultiStructureTab

maestro = schrodinger.get_maestro()


[docs]class TransitionStateMixin(object): NAME = "Transition State" HELP_TOPIC = "JAGUAR_TOPIC_TRANSITION_STATE_FOLDER" SA_LOWEST_HESSIAN = "Lowest Hessian eigenvector" SA_LOWEST_NON_TORSIONAL = "Lowest non-torsional mode" SA_LOWEST_BOND_STRETCH = "Lowest bond-stretch mode" SA_RP_PATH = "Reactant-product path" SA_USER = "User-selected eigenvector" SA_ACTIVE = "Active coordinate eigenvector" SEARCH_ALONG_VALUES = { SA_LOWEST_HESSIAN: mm.MMJAG_ITRVEC_HESSIAN, SA_LOWEST_NON_TORSIONAL: mm.MMJAG_ITRVEC_NONTOR, SA_LOWEST_BOND_STRETCH: mm.MMJAG_ITRVEC_STRETCH, SA_RP_PATH: mm.MMJAG_ITRVEC_QST, SA_ACTIVE: mm.MMJAG_ITRVEC_ACTIVE, SA_USER: SA_USER } STANDARD_SEARCH_ALONG = (SA_LOWEST_HESSIAN, SA_LOWEST_NON_TORSIONAL, SA_LOWEST_BOND_STRETCH, SA_USER) ADDITIONAL_SEARCH_ALONGS = () SEARCH_STANDARD = 0
[docs] def setup(self): super(TransitionStateMixin, self).setup() self.ALL_SEARCH_ALONGS = ((self.STANDARD_SEARCH_ALONG,) + self.ADDITIONAL_SEARCH_ALONGS) self.ui.search_along_combo.currentIndexChanged.connect( self.searchAlongChanged) self.ui.hessian_refine_cb.toggled.connect( self.onHessianRefinementToggled)
[docs] def onHessianRefinementToggled(self, checked): self.ui.low_freq_modes_rb.setEnabled(checked) self.ui.low_freq_modes_sb.setEnabled(checked)
[docs] def repopulateSearchAlongCombo(self, search_method): """ Replace the contents of the search along combo boxes based on the current search method """ current_data = self.ui.search_along_combo.currentData() self.ui.search_along_combo.clear() for name in self.ALL_SEARCH_ALONGS[search_method]: data = self.SEARCH_ALONG_VALUES[name] self.ui.search_along_combo.addItem(name, data) try: self.ui.search_along_combo.setCurrentData(current_data) except ValueError: self.ui.search_along_combo.setCurrentIndex(0)
[docs] def searchAlongChanged(self): """ Respond to the user changing the search along method by updating the status of the eigenvector input widgets """ current_data = self.ui.search_along_combo.currentData() user_selected = (current_data == self.SA_USER) self.ui.eigenvec_lbl.setEnabled(user_selected) self.ui.eigenvec_sb.setEnabled(user_selected) if user_selected: self.ui.follow_same_eigenvec_cb.setEnabled(False) self.ui.follow_same_eigenvec_cb.setChecked(True) elif not self.ui.follow_same_eigenvec_cb.isEnabled(): self.ui.follow_same_eigenvec_cb.setEnabled(True) self.ui.follow_same_eigenvec_cb.setChecked(False)
def _getSearchAlongAndHessianKeywords(self): """ Get the keywords for the search along and Hessian settings :return: A keyword dictionary :rtype: dict """ keywords = {} search_along = self.ui.search_along_combo.currentData() if search_along == self.SA_USER: search_along = self.ui.eigenvec_sb.value() keywords[mm.MMJAG_IKEY_ITRVEC] = search_along if self.ui.follow_same_eigenvec_cb.isChecked(): follow_same = mm.MMJAG_IFOLLOW_ON else: follow_same = mm.MMJAG_IFOLLOW_OFF keywords[mm.MMJAG_IKEY_IFOLLOW] = follow_same if self.ui.hessian_refine_cb.isChecked(): hessian_refine = self.ui.low_freq_modes_sb.value() else: hessian_refine = 0 keywords[mm.MMJAG_IKEY_NHESREF] = hessian_refine return keywords def _loadSearchMethod(self, jag_input): """ Load the search method settings Should be defined for subclasses that have multiple search methods :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ def _loadSearchAlong(self, jag_input): """ Load the search along settings :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ search_along = jag_input[mm.MMJAG_IKEY_ITRVEC] if search_along > 0: self.ui.search_along_combo.setCurrentData(self.SA_USER) self.ui.eigenvec_sb.setValue(search_along) elif search_along not in self.SEARCH_ALONG_VALUES.values(): msg = "Unrecognized value for search along: %s=%s" % ( mm.MMJAG_IKEY_ITRVEC, search_along) warnings.warn(JaguarSettingWarning(msg)) else: try: self.ui.search_along_combo.setCurrentData(search_along) except ValueError: if jag_input.isNonDefault(mm.MMJAG_IKEY_ITRVEC): # Only bother to display a warning if the user has # explicitly specified the search along setting, so that the # user doesn't get warned about a default setting. msg = ("Search along setting (%s=%s) cannot be used with " "currently specified search method." % (mm.MMJAG_IKEY_ITRVEC, search_along)) warnings.warn(JaguarSettingWarning(msg)) follow_same = (jag_input[mm.MMJAG_IKEY_IFOLLOW] != mm.MMJAG_IFOLLOW_OFF) if search_along > 0 and not follow_same: msg = ("Follow same eigenvector must be enabled when searching " "along a user-selected eigenvector. Assuming %s=%s)" % (mm.MMJAG_IKEY_IFOLLOW, mm.MMJAG_IFOLLOW_ON)) warnings.warn(JaguarSettingWarning(msg)) follow_same = True self.ui.follow_same_eigenvec_cb.setChecked(follow_same) def _loadHessianRefinement(self, jag_input): """ Load the hessian refinement settings :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ hessian_refine = jag_input[mm.MMJAG_IKEY_NHESREF] if hessian_refine < 0: msg = "Value for %s must be non-negative (%s=%s)." % ( mm.MMJAG_IKEY_NHESREF, mm.MMJAG_IKEY_NHESREF, hessian_refine) warnings.warn(JaguarSettingWarning(msg)) elif hessian_refine == 0: self.ui.hessian_refine_cb.setChecked(False) else: self.ui.hessian_refine_cb.setChecked(True) self.ui.low_freq_modes_sb.setValue(hessian_refine)
[docs] def getMmJagKeywords(self): keywords = super(TransitionStateMixin, self).getMmJagKeywords() keywords.update(self._getSearchAlongAndHessianKeywords()) return keywords
[docs] def loadSettings(self, jag_input): super(TransitionStateMixin, self).loadSettings(jag_input) self._loadSearchMethod(jag_input) self._loadSearchAlong(jag_input) self._loadHessianRefinement(jag_input)
[docs]class TransitionStateTab(TransitionStateMixin, MultiStructureTab): """ Transition State tab. :cvar searchMethodChanged: a signal emitted when the search method changes :vartype searchMethodChanged: PyQt5.QtCore.pyqtSignal """ UI_MODULES = (ui.transition_state_top_ui, ui.multi_structures_ui, ui.transition_state_bottom_ui, (MoleculeTabNoInputSelector, "molecule_sub_tab")) LST_SEARCH_ALONG = (TransitionStateMixin.SA_LOWEST_HESSIAN, TransitionStateMixin.SA_LOWEST_NON_TORSIONAL, TransitionStateMixin.SA_LOWEST_BOND_STRETCH, TransitionStateMixin.SA_RP_PATH, TransitionStateMixin.SA_USER) QST_SEARCH_ALONG = (TransitionStateMixin.SA_RP_PATH,) ADDITIONAL_SEARCH_ALONGS = (LST_SEARCH_ALONG, QST_SEARCH_ALONG) SEARCH_LST, SEARCH_QST = list(range(1, 3)) searchMethodChanged = QtCore.pyqtSignal(int)
[docs] def setup(self): super(TransitionStateTab, self).setup() self.ui.search_method_btngrp.setId(self.ui.standard_rb, self.SEARCH_STANDARD) self.ui.search_method_btngrp.setId(self.ui.lst_rb, self.SEARCH_LST) self.ui.search_method_btngrp.setId(self.ui.qst_rb, self.SEARCH_QST) self.ui.search_method_btngrp.buttonClicked.connect( self.onSearchMethodChanged) init_lst_guess_validator = QtGui.QDoubleValidator() init_lst_guess_validator.setDecimals(2) self.ui.init_lst_guess_le.setValidator(init_lst_guess_validator) self.onSearchMethodChanged()
[docs] def onSearchMethodChanged(self): """ Respond to the user changing the search method by updating the structure selectors and the search along combo box """ search_method = self.ui.search_method_btngrp.checkedId() if search_method == self.SEARCH_STANDARD: self.setStructureSelectorsEnabled(True, False, False) elif search_method == self.SEARCH_LST: self.setStructureSelectorsEnabled(False, True, True) elif search_method == self.SEARCH_QST: self.setStructureSelectorsEnabled(True, True, True) self.repopulateSearchAlongCombo(search_method) is_lst = search_method == self.SEARCH_LST self.ui.init_lst_guess_lbl.setEnabled(is_lst) self.ui.init_lst_guess_le.setEnabled(is_lst) self.searchMethodChanged.emit(search_method)
[docs] def getStructures(self): """ Get all of the structures that are loaded into the tab. When LST is selected, the structure types don't map to the Z-matrices in the standard way, so we have to override the parent function. :return: A list of: - the structure to be loaded into ZMAT1 (or None) - the structure to be loaded into ZMAT2 (or None) - the structure to be loaded into ZMAT3 (or None) :rtype: list """ search_method = self.ui.search_method_btngrp.checkedId() if search_method == self.SEARCH_STANDARD: entry_types = [self.TRANSITION_STATE, None, None] elif search_method == self.SEARCH_LST: entry_types = [self.REACTANT, self.PRODUCT, None] elif search_method == self.SEARCH_QST: entry_types = self.ALL_STRUCS strucs = [] proj = maestro.project_table_get() for entry_type in entry_types: if entry_type is not None: strucs.append(self._getStruc(proj, entry_type)) else: strucs.append(None) return strucs
[docs] def getMmJagKeywords(self): keywords = super(TransitionStateTab, self).getMmJagKeywords() keywords.update(self._getSearchMethodKeyword()) return keywords
def _getSearchMethodKeyword(self): """ Get the keyword for the search method :return: A keyword dictionary :rtype: dict """ keywords = {} search_method = self.ui.search_method_btngrp.checkedId() if search_method == self.SEARCH_STANDARD: search_method_value = mm.MMJAG_IQST_OFF else: search_method_value = mm.MMJAG_IQST_STQN keywords[mm.MMJAG_IKEY_IQST] = search_method_value if search_method != self.SEARCH_LST: keywords[mm.MMJAG_RKEY_QSTINIT] = None elif not self.ui.init_lst_guess_le.hasAcceptableInput(): msg = ("Unacceptable value for initial LST guess: %s" % self.ui.init_lst_guess_le.text()) raise JaguarSettingError(msg) else: lst_guess = float(self.ui.init_lst_guess_le.text()) keywords[mm.MMJAG_RKEY_QSTINIT] = lst_guess return keywords def _loadSearchMethod(self, jag_input): """ Load the search method settings :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ iqst = jag_input[mm.MMJAG_IKEY_IQST] if iqst == mm.MMJAG_IQST_OFF: self.ui.standard_rb.setChecked(True) elif iqst != mm.MMJAG_IQST_STQN: msg = "Unrecognized value for search method (%s=%s)" % ( mm.MMJAG_IKEY_IQST, iqst) warnings.warn(JaguarSettingWarning(msg)) elif self.isLst(jag_input): self.ui.lst_rb.setChecked(True) else: self.ui.qst_rb.setChecked(True) self.onSearchMethodChanged() init_guess = jag_input[mm.MMJAG_RKEY_QSTINIT] self.ui.init_lst_guess_le.setText(str(init_guess)) if (iqst == mm.MMJAG_IQST_OFF and jag_input.isNonDefault(mm.MMJAG_RKEY_QSTINIT)): msg = ( "Initial LST guess provided (%s=%s) but Standard search " "method chosen (%s=%s). Initial LST guess will be ignored." % (mm.MMJAG_RKEY_QSTINIT, init_guess, mm.MMJAG_IKEY_IQST, iqst)) warnings.warn(JaguarSettingWarning(msg))
[docs] def setStructures(self, entry_ids, jag_input=None): """ Override the parent function so we can properly handle LST jobs (which use zmat 1 and 2 for reactant and product instead of transition state and reactant) """ if jag_input is not None and self.isLst(jag_input): last_eid = entry_ids.pop() entry_ids.insert(0, last_eid) super(TransitionStateTab, self).setStructures(entry_ids)
[docs] def isLst(self, jag_input): """ Does the Jaguar input specify an LST job? This answer isn't guaranteed to be correct, since there's no way to distinguish a fully- or partially-specified LST job from a partially-specified QST job if the default initial LST guess is used. If the input is ambiguous and LST or QST is already selected, then we try to keep the setting as is. :param jag_input: A JaguarInput object containing the job settings :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` :return: True if the specified job is likely to be LST. False otherwise. :rtype: bool """ if jag_input.isNonDefault(mm.MMJAG_RKEY_QSTINIT): return True elif jag_input.hasStructure(mm.MMJAG_ZMAT3): return False elif self.ui.standard_rb.isChecked(): # If the input is ambiguous and neither LST nor QST is already # selected, then assume it's LST if there are exactly two # structures, since that would represent a fully-specified LST job. # If it's partially-specified, assume it's QST, since the structure # types for QST match up with those for a standard job and we don't # have any better reasoning to go off of. return (jag_input.hasStructure(mm.MMJAG_ZMAT1) and jag_input.hasStructure(mm.MMJAG_ZMAT2)) else: return self.ui.lst_rb.isChecked()
[docs]class ActiveTransitionStateTab(TransitionStateTab): """ Tab used in place of transition state tab in Transition State Search panel """ STANDARD_SEARCH_ALONG = (TransitionStateTab.STANDARD_SEARCH_ALONG + (TransitionStateMixin.SA_ACTIVE,))
[docs] def setup(self): """Setup UI elements""" self.constraints_are_active = False super().setup() self.ui.active_coords_rb = QtWidgets.QRadioButton( "Use active coordinates") self.ui.active_coords_rb.setEnabled(False) self.ui.active_coords_rb.setToolTip( "Use the active coordinates to refine " "the Hessian. Requires constraints to " "be set as active coordinates in the " "Optimization tab.") self.ui.hessian_refine_opt_layout.insertWidget(2, self.ui.active_coords_rb)
[docs] def onSearchMethodChanged(self): # See base class for documentation. super().onSearchMethodChanged() search_method = self.ui.search_method_btngrp.checkedId() # When search method is not standard 'active' constraints are # disabled in the Optimization tab, but signal is not emitted. if search_method != self.SEARCH_STANDARD: self.constraints_are_active = False
[docs] def getActiveCoordItem(self): """ Return the combobox item corresponding to "Active coordinate eigenvector" (itrvec=-6) or None if combobox does not have this item. :returns: The model item corresponding to the Active Coordinates option in the search-along combo box :rtype: QtGui.QStandardItem or None """ active_idx = self.ui.search_along_combo.findData(mm.MMJAG_ITRVEC_ACTIVE) cb_model = self.ui.search_along_combo.model() active_item = cb_model.item(active_idx) return active_item
[docs] def onActiveConstraintsChkChanged(self, state): """ Enable or disable relevant UI elements when constraints are set as active on the optimization tab. :param state: Checked or not checked :type state: bool """ active_item = self.getActiveCoordItem() active_item.setEnabled(state) self.constraints_are_active = state if state: self.ui.search_along_combo.setCurrentData(mm.MMJAG_ITRVEC_ACTIVE) self.ui.hessian_refine_cb.setChecked(True) self.ui.active_coords_rb.setChecked(True) self.ui.active_coords_rb.setEnabled(True) else: if self.ui.search_along_combo.currentData() == \ mm.MMJAG_ITRVEC_ACTIVE: self.ui.search_along_combo.setCurrentData( mm.MMJAG_ITRVEC_STRETCH) if self.ui.active_coords_rb.isChecked(): self.ui.hessian_refine_cb.setChecked(False) self.ui.low_freq_modes_rb.setChecked(True) self.ui.active_coords_rb.setEnabled(False)
[docs] def onHessianRefinementToggled(self, checked): """ Enable active coordinates radio button if active constraints :param checked: checked or not checked :type checked: bool """ super().onHessianRefinementToggled(checked) if self.constraints_are_active: self.ui.active_coords_rb.setEnabled(checked)
def _getSearchAlongAndHessianKeywords(self): """ Get the keywords for the search along and Hessian settings :return: A keyword dictionary :rtype: dict """ keywords = super()._getSearchAlongAndHessianKeywords() if (self.ui.hessian_refine_cb.isChecked() and self.ui.active_coords_rb.isChecked()): keywords[mm.MMJAG_IKEY_NHESREF] = mm.MMJAG_NHESREF_ACTIVE return keywords def _loadHessianRefinement(self, jag_input): """ Load the hessian refinement settings. Overloads parent class definition :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ hessian_refine = jag_input[mm.MMJAG_IKEY_NHESREF] if hessian_refine < -1: msg = "Value for %s must be greater than -1 (%s=%s)." % ( mm.MMJAG_IKEY_NHESREF, mm.MMJAG_IKEY_NHESREF, hessian_refine) warnings.warn(JaguarSettingWarning(msg)) elif hessian_refine == 0: self.ui.hessian_refine_cb.setChecked(False) else: self.ui.hessian_refine_cb.setChecked(True) if hessian_refine == mm.MMJAG_NHESREF_ACTIVE: # Corresponds to -1 self.ui.active_coords_rb.setChecked(True) else: # positive value self.ui.active_coords_rb.setChecked(False) self.ui.low_freq_modes_sb.setValue(hessian_refine)
[docs] def repopulateSearchAlongCombo(self, search_method): """ Replace the contents of the search along combo boxes based on the current search method """ super().repopulateSearchAlongCombo(search_method) search_method = self.ui.search_method_btngrp.checkedId() if search_method == self.SEARCH_STANDARD: active_item = self.getActiveCoordItem() active_item.setEnabled(self.constraints_are_active) tooltip = ("Select the eigenvector that best overlaps the active " "coordinates. Requires constraints to be set as active " "coordinates in the Optimization tab.") active_item.setData(tooltip, Qt.ToolTipRole)
[docs] def loadSettings(self, jag_input): """ Restore settings from Jaguar input handle. :param jag_input: The Jaguar settings to base the tab settings on :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ super().loadSettings(jag_input) self.constraints_are_active = jag_input.anyConstraintsActive() if not self.constraints_are_active: if jag_input[mm.MMJAG_IKEY_NHESREF] == mm.MMJAG_NHESREF_ACTIVE: self.ui.low_freq_modes_rb.setChecked(True) self.ui.hessian_refine_cb.setChecked(False) msg = "Cannot use Hessian Refinement with active " \ "coordinates. " warnings.warn(JaguarSettingWarning(msg)) if jag_input[mm.MMJAG_IKEY_ITRVEC] == mm.MMJAG_ITRVEC_ACTIVE: msg = "Cannot search along active coordinates. " warnings.warn(JaguarSettingWarning(msg))