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

import warnings
from collections import OrderedDict
from collections import namedtuple

from schrodinger.application.jaguar.input import JaguarInput
from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets

from .. import theory_selector
from .. import ui
from ..theory_tab_widgets import SpinExcitedStateController
from ..utils import THEORY_DFT
from ..utils import THEORY_HF
from ..utils import THEORY_LMP2
from ..utils import THEORY_RIMP2
from ..utils import JaguarSettingWarning
from ..utils import SpinTreatment
from .base_tab import BaseTab
from .base_tab import LoadsTheoryMixin
from .base_tab import ProvidesTheoryMixin

THEORY_LEVEL_TO_COMBO_TEXT_MAP = {
    "DFT (Density Functional Theory)": THEORY_DFT,
    "HF (Hartree-Fock)": THEORY_HF,
    "LMP2 (Local MP2)": THEORY_LMP2,
    "RI-MP2 (Resolution-of-identity MP2)": THEORY_RIMP2
}

# Named tuple that contains excited state settings data
ExcitedStateInfo = namedtuple(
    'ExcitedStateInfo', 'excited_state excited_state_mode singlet triplet')
NOSOZORA_HAMILTONIAN = {
    "Nonrelativistic": "",
    "Scalar ZORA (relativistic)": mm.MMJAG_RELHAM_ZORA_1C
}


[docs]class TheoryTab(LoadsTheoryMixin, BaseTab): """ A tab for selecting the level of theory. :cvar spinTreatmentChanged: A signal emitted when the spin treatment changes. No arguments are emitted. Note that this signal is not emitted when the theory level changes from DFT to HF, even if that results in a change in the state of the spin unrestricted checkbox. The theory_level_changed signal will be emitted in those cases, however, and the state of the spin unrestricted checkbox can be queried via `getSpinUnrestricted`. :vartype spinTreatmentChanged: `PyQt5.QtCore.pyqtSignal` :cvar excited_state_changed: A signal emitted when the state of the 'Excited state' checkbox changes. No arguments are emitted. The state of the excited state checkbox can be queried via `getExcitedState`. :vartype excited_state_changed: `PyQt5.QtCore.pyqtSignal` :cvar hamiltonianChanged: A signal emitted when the Hamiltonian changes. No arguments are emitted. The current Hamiltonian can be queried via `getHamiltonian`. :vartype hamiltonianChanged: `PyQt5.QtCore.pyqtSignal` :cvar THEORY_INDICES: A dictionary of {index in the theory combo box: theory name}. Note that this combo box is populated in the ui file, so this dictionary must agree with the ui file. :vartype THEORY_INDICES: dict :cvar THEORY_TO_INDEX: A dictionary of {theory name: index in the theory drop down} (i.e. the reverse of `THEORY_INDICES`) :vartype THEORY_TO_INDEX: dict :cvar CORE_LOCALIZATION_METHODS: An OrderedDict of {localization method: mmjag keyword value} used to populate the core localization combo box. :type CORE_LOCALIZATION_METHOD: `collections.OrderedDict` :cvar VALENCE_LOCALIZATION_METHODS: An OrderedDict of {localization method: mmjag keyword value} used to populate the valence localization combo box. :vartype VALENCE_LOCALIZATION_METHODS: `collections.OrderedDict` :cvar RESONANCE: An OrderedDict of {resonance method: mmjag keyword value} used to populate the resonance combo box. :type CORE_LOCALIZATION_METHOD: `collections.OrderedDict` :cvar LMP2_KEYWORDS: A set of keywords that may only appear if the level of theory is LMP2. This set is used in `loadSettings` for validation purposes. :vartype LMP2_KEYWORDS: frozenset :cvar DFT_OR_HF_KEYWORDS: A set of keywords that may only appear if the level of theory is HF or DFT. This set is used in `loadSettings` for validation purposes. :vartype DFT_OR_HF_KEYWORDS: frozenset :cvar DFT_GRID_KEYWORDS: A set of keywords that correspond to DFT grid density. This set is used in `loadSettings` for validation purposes. :vartype DFT_GRID_KEYWORDS: frozenset :cvar DFT_ONLY_KEYWORDS: A set of keywords that may only appear if the level of theory is DFT. This set is used in `loadSettings` for validation purposes. Note that keywords in `DFT_GRID_KEYWORDS` do not need to be repeated here. :vartype DFT_ONLY_KEYWORDS: frozenset :cvar GRID_DENSITY: An OrderedDict of {grid density name: (gdftmed value, gdftfine value, gdftgrad value)} used to populate the grid density combo box. :vartype GRID_DENSITY: `collections.OrderedDict` :cvar GRID_DENSITY_REV: An OrderedDict of {(gdftmed value, gdftfine value, gdftgrad value): grid density name} (i.e. the reverse of `GRID_DENSITY`) :vartype GRID_DENSITY_REV: `collections.OrderedDict` :cvar EXCITED_STATE_CB_TEXT: Text that will be shown for the excited state check box. :vartype EXCITED_STATE_CB_TEXT: str """ NAME = "Theory" HELP_TOPIC = "JAGUAR_TOPIC_THEORY_FOLDER" UI_MODULES = (ui.theory_tab_ui,) THEORY_COMBO_DFT, THEORY_COMBO_HF, THEORY_COMBO_LMP2, THEORY_COMBO_RIMP2 = list( range(4)) THEORY_INDICES = { THEORY_COMBO_DFT: THEORY_DFT, THEORY_COMBO_HF: THEORY_HF, THEORY_COMBO_LMP2: THEORY_LMP2, THEORY_COMBO_RIMP2: THEORY_RIMP2 } THEORY_TO_INDEX = {v: k for k, v in THEORY_INDICES.items()} CORE_LOCALIZATION_METHODS = OrderedDict( (("None", mm.MMJAG_LOCLMP2C_OFF), ("Pipek-Mezey", mm.MMJAG_LOCLMP2C_ATOM), ("Boys", mm.MMJAG_LOCLMP2C_BOYS), ("Pipek-Mezey (alt)", mm.MMJAG_LOCLMP2C_BASIS))) VALENCE_LOCALIZATION_METHODS = OrderedDict( (("Pipek-Mezey", mm.MMJAG_LOCLMP2V_ATOM), ("Boys", mm.MMJAG_LOCLMP2V_BOYS), ("Pipek-Mezey (alt)", mm.MMJAG_LOCLMP2V_BASIS))) RESONANCE = OrderedDict((("None", mm.MMJAG_IRESON_OFF), ("Partial delocalization", mm.MMJAG_IRESON_ON), ("Full delocalization", mm.MMJAG_IRESON_FULL))) LMP2_KEYWORDS = frozenset( (mm.MMJAG_IKEY_MP2, mm.MMJAG_IKEY_LOCLMP2C, mm.MMJAG_IKEY_LOCLMP2V, mm.MMJAG_IKEY_IRESON, mm.MMJAG_IKEY_IHETER)) DFT_OR_HF_KEYWORDS = frozenset((mm.MMJAG_IKEY_IUHF, mm.MMJAG_IKEY_ICIS, mm.MMJAG_IKEY_NROOT, mm.MMJAG_IKEY_MAXCIIT)) DFT_GRID_KEYWORDS = frozenset( (mm.MMJAG_IKEY_GDFTMED, mm.MMJAG_IKEY_GDFTFINE, mm.MMJAG_IKEY_GDFTGRAD)) DFT_ONLY_KEYWORDS = frozenset((mm.MMJAG_SKEY_DFTNAME, mm.MMJAG_IKEY_IDFT)) GRID_DENSITY = OrderedDict( (("Medium", (-10, -11, -12)), ("Fine", (-13, -13, -13)), ("Maximum", (-14, -14, -14)))) GRID_DENSITY_REV = {v: k for k, v in GRID_DENSITY.items()} EXCITED_STATE_TYPES = OrderedDict( (("Singlet", (mm.MMJAG_RSINGLET_ON, mm.MMJAG_RTRIPLET_OFF)), ("Triplet", (mm.MMJAG_RSINGLET_OFF, mm.MMJAG_RTRIPLET_ON)), ("Singlet & Triplet", (mm.MMJAG_RSINGLET_ON, mm.MMJAG_RTRIPLET_ON)))) HAMILTONIAN = OrderedDict(( ("Nonrelativistic", ""), ("Scalar ZORA (relativistic)", mm.MMJAG_RELHAM_ZORA_1C), # Spin-orbit ZORA is blocked for the 15-3 release. See PANEL-3901. # ("Spin-orbit ZORA (relativistic)", mm.MMJAG_RELHAM_ZORA_2C) )) EXCITED_STATE_CB_TEXT = "Excited state (TDDFT)" spinTreatmentChanged = QtCore.pyqtSignal() excited_state_changed = QtCore.pyqtSignal() hamiltonianChanged = QtCore.pyqtSignal()
[docs] def setup(self): self.ui.theory_combo.addItemsFromDict(THEORY_LEVEL_TO_COMBO_TEXT_MAP) self._populateExcitedStates() self._populateLmp2Combos() self._populateGridDensity() for hamiltonian_combo in (self.ui.dft_hamiltonian_combo, self.ui.hf_hamiltonian_combo): hamiltonian_combo.addItemsFromDict(self.HAMILTONIAN) self.ui.dft_excited_state_widget.setExcitedStateCheckBoxText( self.EXCITED_STATE_CB_TEXT) self.ui.hf_excited_state_widget.setExcitedStateCheckBoxText( self.EXCITED_STATE_CB_TEXT) self.ui.theory_combo.currentIndexChanged.connect( self._levelOfTheoryChanged) self.ui.dft_excited_state_widget.excited_state_changed.connect( self.excited_state_changed.emit) self.ui.hf_excited_state_widget.excited_state_changed.connect( self.excited_state_changed.emit) for hamiltonian_combo in [ self.ui.dft_hamiltonian_combo, self.ui.hf_hamiltonian_combo ]: hamiltonian_combo.currentIndexChanged.connect( self.hamiltonianChanged.emit) self.dft_controller = SpinExcitedStateController( self.ui.dft_spin_widget, self.ui.dft_excited_state_widget, self.ui.dft_hamiltonian_combo) self.hf_controller = SpinExcitedStateController( self.ui.hf_spin_widget, self.ui.hf_excited_state_widget, self.ui.hf_hamiltonian_combo) # make sure spinTreatmentChanged signals in SpinExcitedStateController # objects are connected before the tab's spinTreatmentChanged signal. self.ui.dft_spin_widget.spinTreatmentChanged.connect( self.spinTreatmentChanged.emit) self.ui.hf_spin_widget.spinTreatmentChanged.connect( self.spinTreatmentChanged.emit) dft_settings_widgets = (self.ui.dft_grid_density_combo, self.ui.dft_spin_widget, self.ui.dft_excited_state_widget, self.ui.use_three_body_cb, self.ui.dft_hamiltonian_combo) hf_settings_widgets = (self.ui.hf_spin_widget, self.ui.hf_excited_state_widget, self.ui.hf_hamiltonian_combo) lmp2_settings_widgets = (self.ui.lmp2_core_loc_combo, self.ui.lmp2_valence_loc_combo, self.ui.lmp2_resonance_combo, self.ui.lmp2_all_atoms_rb, self.ui.lmp2_hetero_atoms_rb) rimp2_settings_widgets = (self.ui.freeze_core_cb,) self._theory_widgets_map = { THEORY_DFT: dft_settings_widgets, THEORY_HF: hf_settings_widgets, THEORY_LMP2: lmp2_settings_widgets, THEORY_RIMP2: rimp2_settings_widgets } self._levelOfTheoryChanged()
def _populateExcitedStates(self): """ Populate spin excited types combo boxes for DFT and HF theory levels using `EXCITED_STATE_TYPES` """ for w in [ self.ui.dft_excited_state_widget, self.ui.hf_excited_state_widget ]: w.populateExcitedStatesType(self.EXCITED_STATE_TYPES) def _populateLmp2Combos(self): """ Populate the LMP2 combo boxes using `CORE_LOCALIZATION_METHODS`, `VALENCE_LOCALIZATION_METHODS`, and `RESONANCE` """ # The LMP2 combo boxes have been promoted to # L{schrodinger.application.jaguar.gui.utils.EnhancedComboBox}s in # Designer, so this class provides the addItemsFromDict() function self.ui.lmp2_core_loc_combo.addItemsFromDict( self.CORE_LOCALIZATION_METHODS) self.ui.lmp2_valence_loc_combo.addItemsFromDict( self.VALENCE_LOCALIZATION_METHODS) self.ui.lmp2_resonance_combo.addItemsFromDict(self.RESONANCE) def _populateGridDensity(self): """ Populate the grid density dropdown using `GRID_DENSITY` """ self.ui.dft_grid_density_combo.addItemsFromDict(self.GRID_DENSITY)
[docs] def getSpinTreatment(self, theory_level): """ Return whether spin unrestricted check box is checked or not :param theory_level: Theory level to get spin treatment for. :type theory_level: str :return: The appropriate spin treatment for the specified theory level. :rtype: bool or NoneType """ if theory_level in [THEORY_LMP2, THEORY_RIMP2]: return SpinTreatment.NA elif theory_level == THEORY_DFT: return self.ui.dft_spin_widget.getSpinTreatment() elif theory_level == THEORY_HF: return self.ui.hf_spin_widget.getSpinTreatment() else: raise ValueError("Unrecognized theory level: %s" % theory_level)
[docs] def getExcitedState(self): """ Return whether excited state check box is checked or not :return: If DFT or HF is currently selected, returns ExcitedStateInfo object that contains the state of the excited state checkbox, excited state mode (ITDA) and whether excited state type 'Singlet' is selected. If LMP2 is currently selected (which has no spin unrestricted check box), ExcitedStateInfo values are None. :rtype: ExcitedStateInfo """ theory_level = self.getTheoryLevel() if theory_level in [THEORY_LMP2, THEORY_RIMP2]: return ExcitedStateInfo(None, None, None, None) elif theory_level == THEORY_DFT: excited_state_widget = self.ui.dft_excited_state_widget elif theory_level == THEORY_HF: excited_state_widget = self.ui.hf_excited_state_widget else: raise ValueError("Unrecognized theory level: %s" % theory_level) excited_state = excited_state_widget.getExcitedState() jag_kw = excited_state_widget.getMmJagKeywords() mode = jag_kw[mm.MMJAG_IKEY_ITDA] singlet = jag_kw[mm.MMJAG_IKEY_RSINGLET] triplet = jag_kw[mm.MMJAG_IKEY_RTRIPLET] return ExcitedStateInfo(excited_state, mode, singlet, triplet)
[docs] def getTheoryLevel(self): """ Return the current theory level being used for tab settings. :return: The current theory level :rtype: str """ theory_str = self.ui.theory_combo.currentText() if THEORY_DFT in theory_str: return THEORY_DFT elif THEORY_HF in theory_str: return THEORY_HF elif THEORY_LMP2 in theory_str: return THEORY_LMP2 elif THEORY_RIMP2 in theory_str: return THEORY_RIMP2
[docs] def setTheoryLevel(self, theory_level): """ Set the current level of theory. :param theory_level: Level of theory to be set. Should be one of THEORY_DFT, THEORY_HF, THEORY_LMP2 or THEORY_RIMP2 :type theory_level: str """ try: self.ui.theory_combo.setCurrentData(theory_level) except ValueError: raise ValueError(f"Unrecognized level of theory {theory_level}")
[docs] def getHamiltonian(self): """ Return the current Hamiltonian setting :return: The current Hamiltonian setting :rtype: str """ theory_level = self.getTheoryLevel() if theory_level == THEORY_DFT: return self.ui.dft_hamiltonian_combo.currentData() elif theory_level == THEORY_HF: return self.ui.hf_hamiltonian_combo.currentData() else: return None
[docs] def getMmJagKeywords(self, theory_level=None): """ Return a dictionary of Jaguar keywords. :param theory_level: Theory level to get the keywords for. If not specified, settings will be returned for the current theory_combo selection. :type theory_level: str """ if theory_level is None: theory_level = self.getTheoryLevel() keywords = {} keywords.update(self._getGridKeywords(theory_level)) keywords.update(self._getSpinAndExcitedStateKeywords(theory_level)) keywords.update(self._getMp2Keywords(theory_level)) keywords.update(self._getHamiltonianKeywords(theory_level)) return keywords
def _getGridKeywords(self, theory_level): """ Get all keywords related to grid density :param theory_level: Theory level to get the keywords for. :type theory_level: str :return: A dictionary of grid density keywords :rtype: dict """ is_dft = theory_level == THEORY_DFT if not is_dft: data = (None, None, None) else: data = self.ui.dft_grid_density_combo.currentData() gdftmed, gdftfine, gdftgrad = data keywords = { mm.MMJAG_IKEY_GDFTMED: gdftmed, mm.MMJAG_IKEY_GDFTFINE: gdftfine, mm.MMJAG_IKEY_GDFTGRAD: gdftgrad } d3abc = 1 if is_dft and self.ui.use_three_body_cb.isChecked() else 0 keywords[mm.MMJAG_IKEY_D3ABC] = d3abc return keywords def _getSpinAndExcitedStateKeywords(self, theory_level): """ Get all keywords related to excited state settings :param theory_level: Theory level to get the keywords for. :type theory_level: str :return: A dictionary of excited state keywords :rtype: dict """ # default keywords default_keywords = { mm.MMJAG_IKEY_ITDDFT: mm.MMJAG_ITDDFT_OFF, mm.MMJAG_IKEY_ITDA: mm.MMJAG_ITDA_OFF, mm.MMJAG_IKEY_NROOT: None, mm.MMJAG_IKEY_MITERTD: None, mm.MMJAG_RKEY_ECONTD: None, mm.MMJAG_RKEY_RCONTD: None, mm.MMJAG_IKEY_IUHF: None, mm.MMJAG_IKEY_RSINGLET: None, mm.MMJAG_IKEY_RTRIPLET: None, mm.MMJAG_IKEY_RGENERIC: mm.MMJAG_RGENERIC_OFF } if theory_level in [THEORY_LMP2, THEORY_RIMP2]: return default_keywords elif theory_level == THEORY_DFT: controller = self.dft_controller elif theory_level == THEORY_HF: controller = self.hf_controller else: raise ValueError("Unrecognized theory level: %s" % theory_level) keywords = controller.getMmJagKeywords() return keywords def _getMp2Keywords(self, theory_level): """ Get all keywords related to LMP2 and RIMP2 settings :param theory_level: Theory level to get the keywords for. :type theory_level: str :return: A dictionary of LMP2 or RIMP2 keywords :rtype: dict """ keywords = { mm.MMJAG_IKEY_MP2: None, mm.MMJAG_IKEY_LOCLMP2C: None, mm.MMJAG_IKEY_LOCLMP2V: None, mm.MMJAG_IKEY_IRESON: None, mm.MMJAG_IKEY_IHETER: None, mm.MMJAG_IKEY_RIMP2: None, mm.MMJAG_IKEY_N_FROZEN_CORE: None } if theory_level == THEORY_LMP2: # The LMP2 combo boxes have been promoted to # L{schrodinger.application.jaguar.gui.utils.EnhancedComboBox}s in # Designer. This class provides the currentData() function keywords[mm.MMJAG_IKEY_MP2] = mm.MMJAG_MP2_VALENCE2 core_localization = self.ui.lmp2_core_loc_combo.currentData() keywords[mm.MMJAG_IKEY_LOCLMP2C] = core_localization valence_localization = self.ui.lmp2_valence_loc_combo.currentData() keywords[mm.MMJAG_IKEY_LOCLMP2V] = valence_localization resonance = self.ui.lmp2_resonance_combo.currentData() keywords[mm.MMJAG_IKEY_IRESON] = resonance if self.ui.lmp2_all_atoms_rb.isChecked(): pairs = mm.MMJAG_IHETER_OFF elif self.ui.lmp2_hetero_atoms_rb.isChecked(): pairs = mm.MMJAG_IHETER_ON else: raise ValueError("Unrecognized LMP2 pairs option") keywords[mm.MMJAG_IKEY_IHETER] = pairs elif theory_level == THEORY_RIMP2: keywords[mm.MMJAG_IKEY_RIMP2] = mm.MMJAG_RIMP2_ON if not self.ui.freeze_core_cb.isChecked(): keywords[ mm.MMJAG_IKEY_N_FROZEN_CORE] = mm.MMJAG_N_FROZEN_CORE_NONE return keywords def _getHamiltonianKeywords(self, theory_level): """ Get the hamiltonian keywords for the specified theory level. :param theory_level: Theory level to get the keywords for. :type theory_level: str :return: Dict of hamiltonian keywords :rtype: dict """ if theory_level == THEORY_DFT: value = self.ui.dft_hamiltonian_combo.currentData() elif theory_level == THEORY_HF: value = self.ui.hf_hamiltonian_combo.currentData() else: value = '' return {mm.MMJAG_SKEY_RELHAM: value}
[docs] def loadSettings(self, jag_input): # Make sure the input is sane err = self._checkSettings(jag_input) if err is not None: err += " All theory tab settings will be ignored." warnings.warn(JaguarSettingWarning(err)) return # Set the appropriate level of theory theory_level = self._determineTheoryLevel(jag_input) theory_index = self.THEORY_TO_INDEX[theory_level] self.ui.theory_combo.setCurrentIndex(theory_index) self._loadAndResetSpinAndExcited(jag_input, theory_level) self._loadGridDensity(jag_input) self._loadLmp2Settings(jag_input) self._loadHamiltonianSettings(jag_input)
def _loadAndResetSpinAndExcited(self, jag_input, theory_level): """ Load the specified mmjag spin and excited settings into the given theory level and reset the spin and excited settings for the other theory levels. :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". If `theory_level` is LMP2, both DFT and HF settings will be reset and `jag_input` will be ignored since LMP2 has no spin and excited settings, :type theory_level: str """ default_jag_input = JaguarInput() if theory_level == THEORY_DFT: self._loadSpinAndExcited(jag_input, THEORY_DFT) self._loadSpinAndExcited(default_jag_input, THEORY_HF) elif theory_level == THEORY_HF: self._loadSpinAndExcited(default_jag_input, THEORY_DFT) self._loadSpinAndExcited(jag_input, THEORY_HF) elif theory_level in [THEORY_LMP2, THEORY_RIMP2]: self._loadSpinAndExcited(default_jag_input, THEORY_DFT) self._loadSpinAndExcited(default_jag_input, THEORY_HF) else: raise ValueError("Unrecognized theory level: %s" % theory_level) def _loadSpinAndExcited(self, jag_input, theory_level): """ Load the "spin restriction" and "excited state" settings from a JaguarInput object into the GUI :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" or "HF". :type theory_level: str """ # Figure out which GUI elements to load settings into if theory_level == THEORY_DFT: controller = self.dft_controller elif theory_level == THEORY_HF: controller = self.hf_controller else: raise ValueError("Unrecognized theory level: %s" % theory_level) controller.loadSettings(jag_input) def _loadGridDensity(self, jag_input): """ Load the grid density settings from a JaguarInput object into the GUI. If the settings do not match any of the GUI grid density options, a warning will be issued. :param jag_input: A JaguarInput object containing the settings to load :type jag_input: `schrodinger.application.jaguar.input.JaguarInput` """ gdftmed = jag_input[mm.MMJAG_IKEY_GDFTMED] gdftfine = jag_input[mm.MMJAG_IKEY_GDFTFINE] gdftgrad = jag_input[mm.MMJAG_IKEY_GDFTGRAD] data = (gdftmed, gdftfine, gdftgrad) try: grid_density = self.GRID_DENSITY_REV[data] self.ui.dft_grid_density_combo.setCurrentText(grid_density) except KeyError: msg = ("Grid density keywords (%s=%s, %s=%s, %s=%s) do not match " "any presets" % (mm.MMJAG_IKEY_GDFTMED, gdftmed, mm.MMJAG_IKEY_GDFTFINE, gdftfine, mm.MMJAG_IKEY_GDFTGRAD, gdftgrad)) warnings.warn(JaguarSettingWarning(msg)) d3abc = jag_input[mm.MMJAG_IKEY_D3ABC] self.ui.use_three_body_cb.setChecked(d3abc == 1) def _loadLmp2Settings(self, jag_input): """ Load the LMP2 settings from a JaguarInput object into the GUI. If unknown values are 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` """ self.ui.lmp2_core_loc_combo.setCurrentMmJagData( jag_input, mm.MMJAG_IKEY_LOCLMP2C, "core localization method") self.ui.lmp2_valence_loc_combo.setCurrentMmJagData( jag_input, mm.MMJAG_IKEY_LOCLMP2V, "valence localization method") self.ui.lmp2_resonance_combo.setCurrentMmJagData( jag_input, mm.MMJAG_IKEY_IRESON, "resonance") pairs = jag_input[mm.MMJAG_IKEY_IHETER] if (pairs == mm.MMJAG_IHETER_OFF): self.ui.lmp2_all_atoms_rb.setChecked(True) else: self.ui.lmp2_hetero_atoms_rb.setChecked(True) def _loadHamiltonianSettings(self, jag_input): """ Load the Hamiltonian settings from a JaguarInput object into the GUI. If unknown values are 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` """ for hamiltonian_combo in [ self.ui.dft_hamiltonian_combo, self.ui.hf_hamiltonian_combo ]: hamiltonian_combo.setCurrentMmJagData(jag_input, mm.MMJAG_SKEY_RELHAM, "Hamiltonian") def _levelOfTheoryChanged(self): """ Update visible settings widgets based on current level of theory. """ theory_level = self.getTheoryLevel() for theory_key, widgets in self._theory_widgets_map.items(): show = theory_key == theory_level for widget in widgets: widget.setVisible(show)
[docs]class ProvidesTheoryTheoryTab(ProvidesTheoryMixin, TheoryTab): """ A theory tab that sets theory level/method in addition to theory settings. """
[docs] def setup(self): super().setup() self.ui.theory_lbl.setText("Level of theory:") self.ui.theory_combo.currentIndexChanged.connect( self.method_changed.emit) self._addDftFunctionalWidgets()
def _addDftFunctionalWidgets(self): """ Add widgets for setting the DFT functional to the DFT group box. """ functional_layout = QtWidgets.QHBoxLayout(self) functional_label = QtWidgets.QLabel("Functional:") functional_layout.addWidget(functional_label) self.functional_le = QtWidgets.QLineEdit() functional_layout.addWidget(self.functional_le) self.functional_le.setReadOnly(True) self.functional_chooser_btn = theory_selector.DftTheorySelectorFilterListToolButton( self) self.functional_chooser_btn.setPopupValign( self.functional_chooser_btn.ALIGN_BOTTOM) functional_layout.addWidget(self.functional_chooser_btn) spacer_item = QtWidgets.QSpacerItem(20, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) functional_layout.addItem(spacer_item) self.functional_chooser_btn.popUpClosing.connect( self._checkNewFunctional) self.ui.dft_group_layout.insertLayout(0, functional_layout)
[docs] def getDefaultKeywords(self): """ The mmjag default DFT functional name is "", which would cause a HF level of theory. Instead, set a default functional of B3LYP-D3. :return: Dictionary of default keywords for this tab. :rtype: dict """ return {mm.MMJAG_SKEY_DFTNAME: 'B3LYP-D3'}
[docs] def loadSettings(self, jag_input): super().loadSettings(jag_input) theory_level = self._determineTheoryLevel(jag_input) self._loadAndResetFunctional(jag_input, theory_level)
[docs] def getMmJagKeywords(self): keywords = super().getMmJagKeywords() keywords[mm.MMJAG_SKEY_DFTNAME] = self.getCommonFunctional() return keywords
def _checkNewFunctional(self): """ When the functional list popup closes, check to see if a new functional was set. """ functional = self.functional_chooser_btn.getMethod() if functional and functional != self.functional_le.text(): self.functional_le.setText(functional) self.method_changed.emit()
[docs] def getTheoryLevel(self): # See LoadsTheoryMixin for documentation. return self.ui.theory_combo.currentData()
[docs] def getAllUsedTheoryLevels(self): return tuple([self.getTheoryLevel()])
def _loadAndResetFunctional(self, jag_input, theory_level): # See LoadsTheoryMixin for documentation. try: self.ui.theory_combo.setCurrentData(theory_level) except ValueError: msg = f"Unrecognized level of theory {theory_level}" raise ValueError(msg) if theory_level == THEORY_DFT: self._loadFunctional(jag_input) self._checkNewFunctional() else: self.method_changed.emit() def _getDftFunctional(self): # See LoadsTheoryMixin for documentation. return self.functional_le.text() def _setFunctional(self, functional): # See ProvidesTheoryMixin for documentation. if self.functional_chooser_btn.isItemHidden(functional): self.functional_chooser_btn.clearFilters() success = self.functional_chooser_btn.setMethod(functional) if not success: raise ValueError(f"Unrecognized functional {functional}") self._checkNewFunctional()
[docs] def getTheoryLevelForEid(self, eid): # See ProvidesTheoryMixin for documentation. return self.getTheoryLevel()
[docs] def getMethodForEid(self, eid): # See ProvidesTheoryMixin for documentation. return self.getMethod()
[docs] def getCommonMethod(self): # See ProvidesTheoryMixin for documentation. return self.getMethod()
[docs] def getCommonTheoryLevel(self): # See ProvidesTheoryMixin for documentation. return self.getTheoryLevel()
[docs]class ProvidesTheoryTheoryTabNoSoZora(ProvidesTheoryTheoryTab): """ A ProvidesTheoryTheoryTab that removes the Spin-orbit ZORA option. """ HAMILTONIAN = NOSOZORA_HAMILTONIAN
[docs]class TheoryTabNoSoZora(TheoryTab): """ A theory tab that removes the Spin-orbit ZORA option. Used for panels involving optimizations. """ HAMILTONIAN = NOSOZORA_HAMILTONIAN
[docs]class TheoryTabOptimization(TheoryTabNoSoZora): """ This tab is used for 'Optimization' task. """ EXCITED_STATE_TYPES = OrderedDict( (("Singlet", (mm.MMJAG_RSINGLET_ON, mm.MMJAG_RTRIPLET_OFF)), ("Triplet", (mm.MMJAG_RSINGLET_OFF, mm.MMJAG_RTRIPLET_ON)))) EXCITED_STATE_CB_TEXT = "Optimize first excited state (TDDFT)"
[docs]class TheoryTabWithSoZora(TheoryTab): """ A theory tab that includes Spin-orbit ZORA option. Used for Single Point Energy panel only! """ HAMILTONIAN = OrderedDict( (("Nonrelativistic", ""), ("Scalar ZORA (relativistic)", mm.MMJAG_RELHAM_ZORA_1C), ("Spin-orbit ZORA (relativistic)", mm.MMJAG_RELHAM_ZORA_2C)))
[docs]class ComboRadioSelector(QtWidgets.QWidget): """ This is compound widget for selecting categorized choices. The categories are presented in a combo box, and the choices are shown below as a set of radio buttons that is dynamically generated every time the category is changed. When a category is selected, the previous choice for that category is remembered and re-selected. The choiceChanged signal is emitted any time the current choice is changed. This includes when the category combo is changed by the user and when the current choice is changed programmatically via the setCurrentChoice() method. """ choiceChanged = QtCore.pyqtSignal()
[docs] def __init__(self, category_dict, parent=None): """ Initializes the widget with source data. :param category_dict: a dictionary mapping category name to a list of strings representing all the choices for that category. To specify an ordering for categories in the combobox, use an OrderedDict. :type category_dict: dict of str mapped to list of str """ QtWidgets.QWidget.__init__(self, parent) self.current_rb_list = [] self.saved_choices = {} self.saved_cat = '' self.main_layout = QtWidgets.QVBoxLayout() self.top_layout = QtWidgets.QHBoxLayout() self.choice_layout = QtWidgets.QHBoxLayout() self.setLayout(self.main_layout) self.cat_combo = QtWidgets.QComboBox() self.top_layout.addWidget(self.cat_combo) self.main_layout.addLayout(self.top_layout) self.main_layout.addLayout(self.choice_layout) self.top_layout.addStretch() self.main_layout.setContentsMargins(0, 0, 0, 0) self.setData(category_dict) self.cat_combo.currentIndexChanged.connect(self.onCategoryChanged)
[docs] def setData(self, category_dict): """ Set the source data for this widget. See the constructor documentation for details on the category_dict argument. """ self.category_dict = category_dict self.cat_combo.clear() for cat in category_dict: self.cat_combo.addItem(cat) self.cat_combo.setCurrentIndex(0) self.onCategoryChanged(0)
[docs] def onCategoryChanged(self, index): """ Responds to changes in the combobox selection by updating radio buttons with the choices for the selected category. :param index: index of selected category in combobox. :type index: int """ # Save previous choice if self.saved_cat: self.saved_choices[self.saved_cat] = self.currentChoice() # Remove old radio buttons for rb in self.current_rb_list: self.choice_layout.removeWidget(rb) rb.setParent(None) rb.deleteLater() # Generate new radio buttons cat = self.currentCategory() choices = self.category_dict[cat] saved_choice = self.saved_choices.get(cat, choices[0]) self.current_rb_list = [] self.choice_btn_group = QtWidgets.QButtonGroup() for choice in choices: rb = QtWidgets.QRadioButton(choice) rb.clicked.connect(self.choiceChanged.emit) if choice == saved_choice: rb.setChecked(True) self.current_rb_list.append(rb) self.choice_layout.addWidget(rb) self.choice_btn_group.addButton(rb) self.saved_cat = cat self.choiceChanged.emit()
[docs] def currentChoice(self): """ Returns the currently selected choice as a string. """ rb = self.choice_btn_group.checkedButton() return rb.text()
[docs] def setCurrentChoice(self, choice): """ Sets the currently selected choice, changing the category if necessary. If a choice appears in more than one category, the first occurrence will be selected. :param choice: the choice to be selected in upper case :type choice: str """ choice = str(choice) for cat_idx, choices in enumerate(self.category_dict.values()): try: # Convert choices to upper cases to match with input choice choice_idx = [text.upper() for text in choices].index(choice) except ValueError: continue self.cat_combo.setCurrentIndex(cat_idx) self.onCategoryChanged(cat_idx) rb = self.current_rb_list[choice_idx] rb.setChecked(True) self.choiceChanged.emit() return raise ValueError('Choice %s not found.' % choice)
[docs] def currentCategory(self): """ Returns the currently selected category. """ return str(self.cat_combo.currentText())
[docs]class DFTComboRadioSelector(ComboRadioSelector): """ Customized ComboRadioSelector which adds on the radio button for enabling and disabling the combobox and radio buttons. """
[docs] def __init__(self, category_dict, parent=None): ComboRadioSelector.__init__(self, category_dict, parent) self.main_rb = QtWidgets.QRadioButton('Recommended:') self.top_layout.insertWidget(0, self.main_rb) self.choice_layout.setContentsMargins(20, 0, 0, 10) self.main_rb.toggled.connect(self.setSelectorEnabled)
[docs] def setSelectorEnabled(self, state=True): """ Overrides the parent method because we don't want to disable the radio button itself when it gets de-selected. """ self.cat_combo.setEnabled(state) for rb in self.current_rb_list: rb.setEnabled(state)