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

import schrodinger
from schrodinger import project
from schrodinger.application.jaguar import input as jaginput
from schrodinger.infra import mm
from schrodinger.ui.qt import entryselector

from .base_tab import BaseTab
from .base_tab import ProvidesBasisMixin
from .base_tab import ProvidesStructuresMixin

maestro = schrodinger.get_maestro()


[docs]class MultiStructureTab(ProvidesBasisMixin, ProvidesStructuresMixin, BaseTab): """ A parent class for tabs that allow transition state, reactant, and product structures to be selected. MultiStructureTabs also contain a Molecule subtab. Note that this class is not intended to be directly instantiated and instead should be subclassed (such as the IRC tab or Transition State tabs). """ ALL_STRUCS = list(range(3)) TRANSITION_STATE, REACTANT, PRODUCT = ALL_STRUCS ENTRY_TYPE_NAMES = ("transition state", "reactant", "product") STRUC_DIFF_WARNING = ("Warning: The %s and %s\n" "entries contain different %s. This must\n" "be resolved before running the job.") ATOMS = "atoms" ATOM_LABELS = "atom labels"
[docs] def setup(self): self.ui.transition_state_btn.clicked.connect( lambda: self.chooseEntry(self.TRANSITION_STATE)) self.ui.reactant_btn.clicked.connect( lambda: self.chooseEntry(self.REACTANT)) self.ui.product_btn.clicked.connect( lambda: self.chooseEntry(self.PRODUCT)) self.ui.transition_state_cb.toggled.connect( lambda: self.structureCbToggled(self.TRANSITION_STATE)) self.ui.reactant_cb.toggled.connect( lambda: self.structureCbToggled(self.REACTANT)) self.ui.product_cb.toggled.connect( lambda: self.structureCbToggled(self.PRODUCT)) self.struc_les = [ self.ui.transition_state_le, self.ui.reactant_le, self.ui.product_le ] self.struc_cbs = [ self.ui.transition_state_cb, self.ui.reactant_cb, self.ui.product_cb ] self.basis_changed = self.ui.molecule_sub_tab.basis_changed self.reset()
[docs] def reset(self): """ Clear the stored structures """ self._entry_ids = [None] * 3 self._updateStrucListings() self.ui.molecule_sub_tab.reset()
[docs] def setStructureSelectorsEnabled(self, transition_state, reactant, product): """ Enable or disable the three structure selection rows """ self.ui.transition_state_cb.setEnabled( transition_state and self._entry_ids[self.TRANSITION_STATE] is not None) self.ui.transition_state_lbl.setEnabled(transition_state) self.ui.transition_state_le.setEnabled(transition_state) self.ui.transition_state_btn.setEnabled(transition_state) self.ui.reactant_cb.setEnabled( reactant and self._entry_ids[self.REACTANT] is not None) self.ui.reactant_lbl.setEnabled(reactant) self.ui.reactant_le.setEnabled(reactant) self.ui.reactant_btn.setEnabled(reactant) self.ui.product_cb.setEnabled(product and self._entry_ids[self.PRODUCT] is not None) self.ui.product_lbl.setEnabled(product) self.ui.product_le.setEnabled(product) self.ui.product_btn.setEnabled(product) self._updateStructureWarningLabel()
[docs] def chooseEntry(self, struc_type): """ Open the EntrySelector dialog for the specified structure and save the selected entry. :param struc_type: The structure to select. Must be one of self.TRANSITION_STATE, self.REACTANT, or self.PRODUCT. :type struc_type: int """ entry = entryselector.get_entry(self) if entry is not None: self._entry_ids[struc_type] = entry self._updateStrucListings() self.structureChanged.emit()
def _updateStrucListings(self): """ Update the contents and status of all structure selection widgets based on the contents of self._entry_ids and the structures currently loaded into the project table. """ proj = maestro.project_table_get() for struc_type in self.ALL_STRUCS: eid = self._entry_ids[struc_type] line_edit = self.struc_les[struc_type] row = self._getRow(proj, eid) if row is None: self._entry_ids[struc_type] = None line_edit.setText("") self.struc_cbs[struc_type].setEnabled(False) self.struc_cbs[struc_type].setChecked(False) else: title = row.title line_edit.setText("%s:%s" % (eid, title)) in_workspace = (row.in_workspace != project.NOT_IN_WORKSPACE) enabled = self.struc_les[struc_type].isEnabled() self.struc_cbs[struc_type].setEnabled(enabled) self.struc_cbs[struc_type].setChecked(in_workspace) self._updateStructureWarningLabel() self._updateMoleculeSubTabStruc() def _getRow(self, proj, eid): """ Retrieve the specified row from the project table. If there is no such row, return None. :param proj: The maestro project :type proj: `schrodinger.project.Project` :param eid: The entry ID to retrieve :type eid: str :return: The requested row, or None if there is no such row. :rtype: `schrodinger.project.ProjectRow` or NoneType """ if eid is None: return None try: return proj.getRow(eid) except (ValueError, mm.MmException): return None def _updateMoleculeSubTabStruc(self): """ Update the Molecule sub-tab structure. This structure is used for basis set calculations. """ representative_struc = self.getRepresentativeStructure() self.ui.molecule_sub_tab.setStructure(representative_struc)
[docs] def projectUpdated(self): """ Update the listing of structures when the project is updated. Note that the callback for this function is registered in the base_panel.MultiStructureMixin class so that it can be unregistered when the panel is closed. """ try: self._updateStrucListings() except project.ProjectException: pass
def _updateStructureWarningLabel(self): """ Update the label that warns about mismatched structures """ diff = self._compareStrucs() if diff is None: text = "" else: text = self.STRUC_DIFF_WARNING % diff self.ui.struc_warning_lbl.setText(text)
[docs] def validate(self): """ Make sure that all of the appropriate structures are loaded and that they all have the same atoms. """ for entry_type in self.ALL_STRUCS: if (self.struc_les[entry_type].isEnabled() and self._entry_ids[entry_type] is None): return ("No structure loaded for %s." % self.ENTRY_TYPE_NAMES[entry_type]) diff = self._compareStrucs() if diff is not None: return "The %s and %s entries contain different %s." % diff basis_selector_err = self.ui.molecule_sub_tab.validate() if basis_selector_err: return basis_selector_err
[docs] def structureCbToggled(self, struc_type): """ Respond to the specified structure inclusion checkbox being toggled by including or excluding the relevant structure from the workspace. """ eid = self._entry_ids[struc_type] if eid is None: return if self.struc_cbs[struc_type].isChecked(): include = project.IN_WORKSPACE else: include = project.NOT_IN_WORKSPACE proj = maestro.project_table_get() proj.getRow(eid).in_workspace = include
def _compareStrucs(self): """ Compare all structures loaded into this tab to make sure that they have the same atoms. Two structures are the same if they: - Have the same number of atoms - Have atoms with the same elements in the same order - Have atoms with the same names after Jaguar atom naming is applied :return: None if all structures are the same. Otherwise, returns a tuple of: - The name of the first structure that's different - The name of the second structure that's different - What's different about the structures. This will be "atoms" if there are a different number of atoms or atoms with different elements, and it will be "atom labels" if there are atoms with different names after Jaguar atom naming is applied. :rtype: NoneType or tuple """ entry_types, elems, atom_names = self._getAtomicNumbersAndAtomNames() return self._compareAtomicNumbersAndAtomNames(entry_types, elems, atom_names) def _getAtomicNumbersAndAtomNames(self): """ Retrieve the atomic numbers and atom names for all structures that are relevant to the current job type (i.e. for all rows that are populated and enabled) :return: A tuple of: - A list of entry types for all structures are relevant - A list of [atomic numbers for the first relevant structure, atomic numbers for the second relevant structure, ...] - A list of [atom names for the first relevant structure, atom names for the second relevant structure, ...] :rtype: tuple """ entry_types = [] elems = [] atom_names = [] proj = maestro.project_table_get() for entry_type in self.ALL_STRUCS: struc = self._getStruc(proj, entry_type) if struc is None: continue jaginput.apply_jaguar_atom_naming(struc) non_dummy_atoms = [ atom for atom in struc.atom if atom.atomic_number != -2 ] cur_elems = [atom.atomic_number for atom in non_dummy_atoms] cur_atom_names = [atom.name for atom in non_dummy_atoms] entry_types.append(entry_type) elems.append(cur_elems) atom_names.append(cur_atom_names) return entry_types, elems, atom_names def _compareAtomicNumbersAndAtomNames(self, entry_types, elems, atom_names): """ Compare the provided atomic numbers and atom names and return a description of the differences :param entry_types: A list of entry types for all structures are relevant :type entry_types: list :param elems: A list of [atomic numbers for the first relevant structure, atomic numbers for the second relevant structure, ...] :type elems: list :param atom_names: A list of [atom names for the first relevant structure, atom names for the second relevant structure, ...] :type atom_names: list :return: None if all structures are the same. Otherwise, returns a tuple of: - The name of the first structure that's different - The name of the second structure that's different - What's different about the structures ("atoms" or "atom labels") :rtype: NoneType or tuple """ diff = None if len(elems) > 1: if elems[0] != elems[1]: diff = [0, 1, self.ATOMS] elif atom_names[0] != atom_names[1]: diff = [0, 1, self.ATOM_LABELS] if len(elems) > 2: if elems[0] != elems[2]: diff = [0, 2, self.ATOMS] elif atom_names[0] != atom_names[2]: diff = [0, 2, self.ATOM_LABELS] if diff is not None: diff[0] = self.ENTRY_TYPE_NAMES[entry_types[diff[0]]] diff[1] = self.ENTRY_TYPE_NAMES[entry_types[diff[1]]] diff = tuple(diff) return diff
[docs] def getStructures(self): """ Get all of the structures that are loaded into the tab. Each structure will be a `schrodinger.structure.Structure` object containing the loaded structure, or None if no structure has been loaded. :return: A list of: - the transition state structure - the reactant structure - the product structure :rtype: list """ proj = maestro.project_table_get() strucs = [ self._getStruc(proj, entry_type) for entry_type in self.ALL_STRUCS ] return strucs
def _getStruc(self, proj, entry_type): """ Retrieve the specified structure :param proj: The current project :type proj: `schrodinger.project.Project` :param struc_type: The structure to retrieve. Must be one of self.TRANSITION_STATE, self.REACTANT, or self.PRODUCT. :type struc_type: int """ eid = self._entry_ids[entry_type] if eid is not None and self.struc_les[entry_type].isEnabled(): cur_struc = proj.getRow(eid).getStructure() return cur_struc else: return None
[docs] def setStructures(self, entry_ids, jag_input=None): """ Load the specified entry IDs into the tab :param entry_ids: A list of: - the transition state structure entry ID - the reactant structure entry ID - the product structure entry ID Note that this value must be a list, not a tuple. :type entry_ids: list :param jag_input: A JaguarInput object containing the job settings. Note that this argument is only used for the Transition State tab version of this function in order to determine if the search method is LST, as this will affect how the structures are loaded. :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ self._entry_ids = entry_ids self._updateStrucListings()
[docs] def getRepresentativeStructure(self): """ Retrieve one structure that can be used for basis selector calculations. :return: A single representative structure, or None if no structures are loaded. :rtype: `schrodinger.structure.Structure` or NoneType """ try: proj = maestro.project_table_get() except project.ProjectException: return None for entry_type in self.ALL_STRUCS: cur_struc = self._getStruc(proj, entry_type) if cur_struc is not None: return cur_struc
[docs] def getEids(self): """ Get the entry ids loaded into the tab :return: A tuple of - A list of entry ids loaded into the tab - Whether the structures have matching atoms and atom names (bool) :rtype: tuple """ eids = [eid for eid in self._entry_ids if eid is not None] diff = self._compareStrucs() acceptable = diff is None return (eids, acceptable)
[docs] def getStructureTitleForJobname(self): # See ProvidesStructuresMixin for method documentation struc = self.getRepresentativeStructure() if struc is None: return None else: return struc.title
# The following functions simply call the equivalent Molecule sub-tab # function
[docs] def getDefaultKeywords(self): return self.ui.molecule_sub_tab.getDefaultKeywords()
[docs] def getMmJagKeywords(self): return self.ui.molecule_sub_tab.getMmJagKeywords()
[docs] def getBasis(self, ignored=None): return self.ui.molecule_sub_tab.getBasis()
[docs] def loadSettings(self, jag_input): ret = self.ui.molecule_sub_tab.loadSettings(jag_input) self.structureChanged.emit() return ret