Source code for schrodinger.application.jaguar.gui.theory_tab_widgets
import warnings
from collections import OrderedDict
from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from .ui import excited_state_widget_ui
from .ui import spin_treatment_widget_ui
from .utils import JaguarSettingWarning
from .utils import SpinTreatment
from .utils import validate_le_float_input
CONV_MIN = 1.e-12
CONV_MAX = 1.0
[docs]class SpinTreatmentWidget(QtWidgets.QWidget):
    """
    This widget allows user to specify SCF spin treatment options.
    :ivar spinTreatmentChanged: A signal indicating that the type of spin
        treatment has changed.
    :vartype spinTreatmentChanged: `PyQt5.QtCore.pyqtSignal`
    """
    spinTreatmentChanged = QtCore.pyqtSignal()
[docs]    def __init__(self, parent):
        """
        Initialize widget.
        :param parent: parent of this dialog.
        :type parent: QtCore.QObject
        """
        super(SpinTreatmentWidget, self).__init__(parent)
        self.ui = spin_treatment_widget_ui.Ui_Widget()
        self.ui.setupUi(self)
        self.ui.btngrp.buttonClicked.connect(self.spinTreatmentChanged.emit)
[docs]    def getMmJagKeywords(self):
        """
        This function returns dictionary of mmjag keywords for this widget.
        :return: mmjag keywords dictionary
        :rtype: dict
        """
        treatment = self.getSpinTreatment()
        return {mm.MMJAG_IKEY_IUHF: treatment.value}
[docs]    def loadSettings(self, jag_input):
        """
        Restore spin treatment settings from Jaguar handle.
        :param jag_input: The Jaguar settings to base the widget settings on
        :type jag_input: `schrodinger.application.jaguar.input.JaguarInput`
        """
        iuhf = jag_input[mm.MMJAG_IKEY_IUHF]
        if iuhf == mm.MMJAG_IUHF_ON:
            self.ui.unrestricted_rb.setChecked(True)
        elif iuhf == mm.MMJAG_IUHF_OFF:
            self.ui.restricted_rb.setChecked(True)
        elif iuhf == mm.MMJAG_IUHF_AUTOMATIC:
            self.ui.automatic_rb.setChecked(True)
        else:
            err = "Unrecognized %s setting (%s)." % (mm.MMJAG_IKEY_IUHF, iuhf)
            warnings.warn(JaguarSettingWarning(err))
[docs]    def getSpinTreatment(self):
        """
        Return the current spin treatment setting
        :return: The current spin treatment setting
        :rtype: `SpinTreatment`
        """
        if self.ui.restricted_rb.isChecked():
            return SpinTreatment.Restricted
        elif self.ui.unrestricted_rb.isChecked():
            return SpinTreatment.Unrestricted
        elif self.ui.automatic_rb.isChecked():
            return SpinTreatment.Automatic
[docs]    def unrestrictedAvailable(self):
        """
        Does the current setting allow for an unrestricted spin treatment?
        :return: True if the spin treatment is currently set to unrestricted or
            automatic.  False otherwise.
        :rtype: bool
        """
        return self.getSpinTreatment().unrestrictedAvailable()
[docs]class ExcitedStateWidget(QtWidgets.QWidget):
    """
    This widget allows user to specify 'Excited state' options.
    :ivar excited_state_changed: A signal indicating that the state of
        the 'Excited state' toggle has changed.
    :vartype excited_state_changed: `PyQt5.QtCore.pyqtSignal`
    """
    CALCULATION_MODE = OrderedDict(
        (("Full linear response", mm.MMJAG_ITDA_OFF),
         ("Tamm-Dancoff approximation", mm.MMJAG_ITDA_ON)))
    excited_state_changed = QtCore.pyqtSignal()
[docs]    def __init__(self, parent):
        """
        Initialize widget.
        :param parent: parent of this dialog.
        :type parent: QtCore.QObject
        """
        self.es_type_rev = {}
        super(ExcitedStateWidget, self).__init__(parent)
        self.ui = excited_state_widget_ui.Ui_Widget()
        self.ui.setupUi(self)
        self.ui.excited_state_cb.disabled_checkstate = False
        # setup validators
        self.ui.energy_conv_le.setValidator(
            QtGui.QDoubleValidator(CONV_MIN, CONV_MAX, 5, self))
        self.ui.residual_conv_le.setValidator(
            QtGui.QDoubleValidator(CONV_MIN, CONV_MAX, 5, self))
        # populate calculation mode combo box
        self.ui.excited_state_combo.addItemsFromDict(self.CALCULATION_MODE)
        self.ui.excited_state_cb.toggled.connect(
            self.excited_state_changed.emit)
        self.ui.excited_state_combo.currentIndexChanged.connect(
            self.excited_state_changed.emit)
        self.ui.es_type_combo.currentIndexChanged.connect(
            self.excited_state_changed.emit)
[docs]    def populateExcitedStatesType(self, es_types):
        """
        This function is called to populate combo box that contains
        excited states types.
        :param es_types: dictionary that contains names of excited states
            types and corresponding mmjag keywords.
        :type es_types: `collections.OrderedDict`
        """
        self.ui.es_type_combo.addItemsFromDict(es_types)
        self.es_type_rev = {v: k for k, v in es_types.items()}
[docs]    def enableExcitedStatesType(self, enable):
        """
        This function is used to enable/disable combo box that defines
        excited states type and its label.
        :param enable: True or False to enable or disable widgets
        :type enable: bool
        """
        self.ui.es_lbl.setEnabled(enable)
        self.ui.es_type_combo.setEnabled(enable)
[docs]    def getExcitedState(self):
        """
        Return whether excited state check box is checked or not
        :return: state of excited state check box
        :rtype: bool
        """
        return self.ui.excited_state_cb.isChecked()
[docs]    def enableExcitedState(self, enable):
        """
        This function is used to enable/disable TDDFT check box and
        excited state combo box. When TDDFT check box is disabled it
        will also get unchecked.
        :param enable: True or False to enable or disable widgets
        :type enable: bool
        """
        self.ui.excited_state_cb.setEnabled(enable)
        self.ui.excited_state_combo.setEnabled(enable)
[docs]    def getMmJagKeywords(self):
        """
        This function returns dictionary of mmjag keywords for this widget.
        :return: mmjag keywords dictionary
        :rtype: dict
        """
        itddft = mm.MMJAG_ITDDFT_OFF
        itda = mm.MMJAG_ITDA_OFF
        num_states = None
        max_iter = None
        energy_conv = None
        residual_conv = None
        rsinglet = None
        rtriplet = None
        if self.ui.excited_state_cb.isChecked():
            itddft = mm.MMJAG_ITDA_ON
            itda = self.ui.excited_state_combo.currentData()
            if self.ui.es_type_combo.isEnabled():
                data = self.ui.es_type_combo.currentData()
                rsinglet, rtriplet = data
            num_states = self.ui.excited_states_sb.value()
            max_iter = self.ui.max_iter_sb.value()
            energy_conv = validate_le_float_input(
                self.ui.energy_conv_le,
                "Invalid input for energy convergence threshold field.")
            residual_conv = validate_le_float_input(
                self.ui.residual_conv_le,
                "Invalid input for residual convergence threshold field.")
        keywords = {
            mm.MMJAG_IKEY_ITDDFT: itddft,
            mm.MMJAG_IKEY_ITDA: itda,
            mm.MMJAG_IKEY_RSINGLET: rsinglet,
            mm.MMJAG_IKEY_RTRIPLET: rtriplet,
            mm.MMJAG_IKEY_NROOT: num_states,
            mm.MMJAG_IKEY_MITERTD: max_iter,
            mm.MMJAG_RKEY_ECONTD: energy_conv,
            mm.MMJAG_RKEY_RCONTD: residual_conv
        }
        return keywords
[docs]    def loadSettings(self, jag_input):
        """
        Restore Excited state settings from Jaguar handle.
        :param jag_input: The Jaguar settings to base the widget settings on
        :type jag_input: `schrodinger.application.jaguar.input.JaguarInput`
        """
        tddft = (jag_input[mm.MMJAG_IKEY_ITDDFT] != mm.MMJAG_ITDDFT_OFF)
        self.ui.excited_state_cb.setChecked(tddft)
        self.ui.excited_state_combo.setCurrentMmJagData(jag_input,
                                                        mm.MMJAG_IKEY_ITDA,
                                                        "calculation method")
        rsinglet = jag_input[mm.MMJAG_IKEY_RSINGLET]
        rtriplet = jag_input[mm.MMJAG_IKEY_RTRIPLET]
        if rsinglet == 0 and rtriplet == 0:
            # There may be a better way to deal with this. These settings
            # correspond to disabled combo box. We assume that this is the
            # case and just set index to default (0).
            self.ui.es_type_combo.setCurrentIndex(0)
        else:
            data = (rsinglet, rtriplet)
            try:
                es_type = self.es_type_rev[data]
                self.ui.es_type_combo.setCurrentText(es_type)
            except KeyError:
                msg = ("Excited states type keywords (%s=%s, %s=%s) do not "
                       "match any presets" % (mm.MMJAG_IKEY_RSINGLET, rsinglet,
                                              mm.MMJAG_IKEY_RTRIPLET, rtriplet))
                warnings.warn(JaguarSettingWarning(msg))
        num_states = jag_input[mm.MMJAG_IKEY_NROOT]
        if num_states < 1:
            err = (
                "Number of excited states (%d) should be greater than zero." %
                num_states)
            warnings.warn(JaguarSettingWarning(err))
        else:
            self.ui.excited_states_sb.setValue(num_states)
        max_iter = jag_input[mm.MMJAG_IKEY_MITERTD]
        if max_iter < 1:
            err = ("Maximum TDDFT(TDHF) iterations (%d) should be "
                   "greater than zero." % max_iter)
            warnings.warn(JaguarSettingWarning(err))
        else:
            self.ui.max_iter_sb.setValue(max_iter)
        energy_conv = jag_input[mm.MMJAG_RKEY_ECONTD]
        if energy_conv < CONV_MIN or energy_conv > CONV_MAX:
            err = ("Energy convergence threshold value (%s) is invalid." %
                   str(energy_conv))
            warnings.warn(JaguarSettingWarning(err))
        else:
            self.ui.energy_conv_le.setText(str(energy_conv))
        residual_conv = jag_input[mm.MMJAG_RKEY_RCONTD]
        if residual_conv < CONV_MIN or residual_conv > CONV_MAX:
            err = ("Residual convergence threshold value (%s) is invalid." %
                   str(energy_conv))
            warnings.warn(JaguarSettingWarning(err))
        else:
            self.ui.residual_conv_le.setText(str(residual_conv))
[docs]    def setExcitedStateCheckBoxText(self, txt):
        """
        Set text for the excited state check box widget. This method is
        needed since we want to show different text in the Optimization
        task.
        :param txt: excited state check box text
        :type txt: str
        """
        self.ui.excited_state_cb.setText(txt)
[docs]class HFExcitedStateWidget(ExcitedStateWidget):
    """
    This widget differs from the parent only by some text, which
    is specific to HF theory level.
    """
    EXCITED_STATE_TEXT = "Excited state (TDHF)"
    MAXIMUM_ITER_TEXT = "Maximum TDHF iterations:"
[docs]    def __init__(self, parent):
        """
        Initialize widget.
        :param parent: parent of this dialog.
        :type parent: QtCore.QObject
        """
        super(HFExcitedStateWidget, self).__init__(parent)
        self.ui.excited_state_cb.setText(self.EXCITED_STATE_TEXT)
        self.ui.max_iter_lbl.setText(self.MAXIMUM_ITER_TEXT)
[docs]class SpinExcitedStateController(object):
    """
    Controller to facilitate interaction between widget that defines spin
    treatment options, excited state widget and Hamiltonian widget. This
    controller is needed because excited states type combo box needs to be
    enabled or disabled depending on the state of spin treatment, excited
    state checkbox, and Hamiltonian combo box. This controller is also used
    to set and get mmjag keywords.
    """
[docs]    def __init__(self, spin_widget, es_widget, h_widget):
        """
        Initialize controller, which takes spin treatment widget and excited
        state widgets as arguments and establishes connection between
        the two.
        :param spin_widget: widget that defines sping treatment options
        :type spin_widget: `SpinRestrictedWidget`
        :param es_widget: widget that defines excited state options
        :type: es_widget: `ExcitedStateWidget`
        :param h_widget: combo box that defines Hamiltonian
        :type h_widget: `EnhancedComboBox`
        """
        self.spin_widget = spin_widget
        self.es_widget = es_widget
        self.h_widget = h_widget
        spin_widget.spinTreatmentChanged.connect(self.checkExcitedStatesType)
        es_widget.excited_state_changed.connect(self.checkExcitedStatesType)
        h_widget.currentIndexChanged.connect(self.checkExcitedStatesType)
[docs]    def checkExcitedStatesType(self):
        """
        This function checks whether excited states type widgets should
        be disabled.
        """
        so_zora = self.h_widget.currentData() == mm.MMJAG_RELHAM_ZORA_2C
        if so_zora:
            # when Spin-orbit ZORA is selected excited state type should
            # be disabled regardless of the other options.
            enable = False
        else:
            enable = (self.es_widget.getExcitedState() and
                      not self.spin_widget.unrestrictedAvailable())
        self.es_widget.enableExcitedStatesType(enable)
        # TDDFT should not be available if both 'Unrestricted' spin treatment
        # and Spin-orbit ZORA are selected.
        spin_treatment = self.spin_widget.getSpinTreatment()
        enable_tddft = not (so_zora and
                            spin_treatment == SpinTreatment.Unrestricted)
        self.es_widget.enableExcitedState(enable_tddft)
[docs]    def getMmJagKeywords(self):
        """
        This function returns dictionary of mmjag keywords for both
        spin restricted and excited state widgets.
        :return: mmjag keywords dictionary
        :rtype: dict
        """
        keywords = self.spin_widget.getMmJagKeywords()
        keywords.update(self.es_widget.getMmJagKeywords())
        # if both Spin-orbit ZORA and TDDFT are selected set rgeneric option
        so_zora = self.h_widget.currentData() == mm.MMJAG_RELHAM_ZORA_2C
        if so_zora and self.es_widget.getExcitedState():
            keywords[mm.MMJAG_IKEY_RGENERIC] = mm.MMJAG_RGENERIC_ON
        else:
            keywords[mm.MMJAG_IKEY_RGENERIC] = mm.MMJAG_RGENERIC_OFF
        return keywords
[docs]    def loadSettings(self, jag_input):
        """
        Convenience function that allows to specify both spin treatment
        and excited state options from a given jaguar handle.
        :param jag_input: The Jaguar settings to base the widget settings on
        :type jag_input: `schrodinger.application.jaguar.input.JaguarInput`
        """
        for w in [self.spin_widget, self.es_widget]:
            w.loadSettings(jag_input)