Source code for schrodinger.application.jaguar.gui.theory_selector
"""
Module containing classes for selection of theory in Jaguar GUIs.
Copyright Schrodinger, LLC. All rights reserved.
"""
import csv
from schrodinger.application.jaguar.gui.utils import THEORY_HF
from schrodinger.application.jaguar.gui.utils import THEORY_LMP2
from schrodinger.application.jaguar.jaguar_keyword_utils import LEVELS_OF_THEORY
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from schrodinger.ui.qt import filter_list
from schrodinger.ui.qt import pop_up_widgets
from schrodinger.utils import csv_unicode
NON_DFT_POSTFIX = " (non-DFT)"
DOCS_FIELDS = ['description', 'references']
[docs]class TheoryListWidgetItem(QtWidgets.QListWidgetItem):
    """
    Custom list widget items for theory selection.
    """
[docs]    def __init__(self,
                 text,
                 method=None,
                 is_recommended=False,
                 is_dispersion_corrected_dft=False,
                 is_long_range_corrected_dft=False,
                 is_hybrid_dft=False,
                 is_meta_gga_dft=False,
                 is_gga_dft=False,
                 is_lda_dft=False,
                 is_non_dft=False):
        """
        :param text: Display text for this list item
        :type text: str
        :param method: Actual method name for this list item.
        :type method: str
        :param is_recommended: Whether this list item is recommended
        :type is_recommended: bool
        :param is_dispersion_corrected_dft: Whether this list item is dispersion-corrected DFT
        :type is_dispersion_corrected_dft: bool
        :param is_long_range_corrected_dft: Whether this list item is long-range corrected DFT
        :type is_long_range_corrected_dft: bool
        :param is_hybrid_dft: Whether this list item is hybrid DFT
        :type is_hybrid_dft: bool
        :param is_meta_gga_dft: Whether this list item is meta-GGA DFT
        :type is_meta_gga_dft: bool
        :param is_gga_dft: Whether this list item is GGA DFT
        :type is_gga_dft: bool
        :param is_lda_dft: Whether this list item is LDA DFT
        :type is_lda_dft: bool
        :param is_non_dft: Whether this list item is non-DFT
        :type is_non_dft: bool
        """
        super().__init__(text)
        self.method = method
        self.is_recommended = is_recommended
        self.is_dispersion_corrected_dft = is_dispersion_corrected_dft
        self.is_long_range_corrected_dft = is_long_range_corrected_dft
        self.is_hybrid_dft = is_hybrid_dft
        self.is_meta_gga_dft = is_meta_gga_dft
        self.is_gga_dft = is_gga_dft
        self.is_lda_dft = is_lda_dft
        self.is_non_dft = is_non_dft
[docs]class TheorySelectorFilterListPopUp(filter_list.FilterListPopUp):
    """
    Class allowing for dynamic filtering and selection of methods.
    :cvar DFT_ONLY: Whether only DFT functionals should be included
    :type DFT_ONLY: bool
    """
    DFT_ONLY = False
[docs]    def __init__(self, parent):
        list_items = self._getListItems()
        cbs = self._getFilterCheckBoxes()
        super().__init__(parent, list_items, cbs,
                         'Limit list to matching methods')
    def _getListItems(self):
        """
        :return: A tuple of the list items for the list widget.
        :rtype: tuple(TheoryListWidgetItem)
        """
        list_items = []
        with csv_unicode.reader_open(LEVELS_OF_THEORY) as fh:
            reader = csv.DictReader(fh)
            for row in reader:
                for col in DOCS_FIELDS:
                    del row[col]
                for col, val in row.items():
                    if col != 'method':
                        if val not in ("0", "1"):
                            raise RuntimeError(
                                f"Unexpected {col} value for {row['method']}: {val}"
                            )
                        row[col] = val == '1'
                is_non_dft = row['is_non_dft']
                method = row['method']
                if is_non_dft and self.DFT_ONLY:
                    continue
                disp_name = method
                if is_non_dft:
                    disp_name = disp_name + NON_DFT_POSTFIX
                list_items.append(TheoryListWidgetItem(disp_name, **row))
        return tuple(list_items)
    def _getFilterCheckBoxes(self):
        """
        :return: a tuple of the filter checkboxes for this list
        :rtype: tuple(filter_list.FilterCheckBox)
        """
        recommended_func = lambda li: li.is_recommended
        recommended_cb = filter_list.FilterCheckBox("Recommended",
                                                    recommended_func)
        disp_corrected_func = lambda li: li.is_dispersion_corrected_dft
        disp_corrected_cb = filter_list.FilterCheckBox(
            'Dispersion corrected DFT', disp_corrected_func)
        long_range_func = lambda li: li.is_long_range_corrected_dft
        long_range_cb = filter_list.FilterCheckBox('Long range corrected DFT',
                                                   long_range_func)
        hybrid_func = lambda li: li.is_hybrid_dft
        hybrid_cb = filter_list.FilterCheckBox("Hybrid DFT", hybrid_func)
        meta_gga_func = lambda li: li.is_meta_gga_dft
        meta_gga_cb = filter_list.FilterCheckBox('Meta GGA DFT', meta_gga_func)
        gga_func = lambda li: li.is_gga_dft
        gga_cb = filter_list.FilterCheckBox('GGA DFT', gga_func)
        lda_func = lambda li: li.is_lda_dft
        lda_cb = filter_list.FilterCheckBox("LDA DFT", lda_func)
        non_dft_func = lambda li: li.is_non_dft
        non_dft_cb = filter_list.FilterCheckBox("Non-DFT", non_dft_func)
        cbs = [
            recommended_cb, disp_corrected_cb, long_range_cb, hybrid_cb,
            meta_gga_cb, gga_cb, lda_cb
        ]
        if not self.DFT_ONLY:
            cbs += [non_dft_cb]
        return tuple(cbs)
    def _getListItemTextForTheory(self, theory):
        """
        :return: The list item text that is shown for a specified theory method.
        :rtype: str
        """
        if theory in [THEORY_HF, THEORY_LMP2]:
            theory_text = theory + NON_DFT_POSTFIX
        else:
            theory_text = theory
        return theory_text
[docs]    def setMethod(self, theory=None):
        """
        Set the theory to the specified value.
        :param theory: Theory value to set
        :type theory: str or None
        :return: True if theory was set, False otherwise.
        :rtype: bool
        """
        if theory is None:
            self._list_widget.setCurrentItem(None)
            return
        theory = self._getListItemTextForTheory(theory)
        items = self._list_widget.findItems(theory,
                                            Qt.MatchFlag.MatchFixedString)
        if len(items) != 1:
            return False
        item = items[0]
        if item.isHidden():
            return False
        self._list_widget.setCurrentItem(item)
        return True
[docs]    def getMethod(self):
        """
        :return: The currently selected theory value.
        :rtype: str or None
        """
        current_item = self._list_widget.currentItem()
        if not current_item:
            return None
        return current_item.method
[docs]    def addMethod(self, method, category, display_name=None):
        """
        Add the specified method to the popup's available methods.
        :param method: Method to be added
        :type method: str
        :param category: Category for this method
        :type category: str
        :param display_name: Display name for this method. If not
                             specified, the method value will be used.
        :type display_name: str or None
        """
        if display_name is None:
            display_name = method
        list_item = TheoryListWidgetItem(display_name, method, category)
        self._list_widget.addItem(list_item)
[docs]    def isItemHidden(self, theory):
        # See parent class for argument specification
        theory = self._getListItemTextForTheory(theory)
        return super().isItemHidden(theory)
[docs]class DftTheorySelectorFilterListPopUp(TheorySelectorFilterListPopUp):
    """
    Class allowing for dynamic filtering and selection of DFT functionals
    """
    DFT_ONLY = True
[docs]class TheorySelectorFilterListToolButton(
        filter_list.ToolButtonWithFilterListPopUp):
    """
    Custom tool button with a theory selector filter list pop up.
    """
    POP_UP_CLASS = TheorySelectorFilterListPopUp
[docs]    def getMethod(self):
        """
        :return: the currently selected theory
        :rtype: str or None
        """
        return self._pop_up.getMethod()
[docs]    def setMethod(self, theory=None):
        """
        Set the current theory value.
        :param theory: Theory value to be set
        :type theory: str or None
        :return: True if the theory level was set, False otherwise.
        :rtype: bool
        """
        return self._pop_up.setMethod(theory)
[docs]    def addMethod(self, method, category, display_name=None):
        """
        Add the specified method to the popup's available methods.
        :param method: Method to be added
        :type method: str
        :param category: Category for this method
        :type category: str
        :param display_name: Display name for this method. If not
                             specified, the method value will be used.
        :type display_name: str or None
        """
        self._pop_up.addMethod(method, category, display_name)
[docs]    def applySettings(self, settings):
        """
        Apply the specified filter settings to the pop up
        :param settings: Settings to be applied
        :type settings: dict
        """
        self._pop_up.applySettings(settings)
[docs]class DftTheorySelectorFilterListToolButton(TheorySelectorFilterListToolButton):
    """
    Custom tool button with a theory selector filter list pop up for
    DFT functionals.
    """
    POP_UP_CLASS = DftTheorySelectorFilterListPopUp
[docs]class FilterTheorySelectorReadOnlyLineEdit(pop_up_widgets.LineEditWithPopUp):
    """
    A read-only line edit used as an editor for table models with a TheorySelectorFilterPopUp.
    :ivar filtersChanged: Signal emitted when filters are toggled.
                          emits a dict of current filter settings.
    :type filtersChanged: QtCore.pyQtSignal(dict)
    """
    filtersChanged = QtCore.pyqtSignal(dict)
[docs]    def __init__(self, parent):
        super().__init__(parent, TheorySelectorFilterListPopUp)
        self.setReadOnly(True)
        self._pop_up.filtersChanged.connect(self.filtersChanged)
        # FIXME - The below is intended to assist in
        # closing the popup when embedded in a table, but
        # doesn't seem to be working as expected...
        self.setFocusPolicy(Qt.StrongFocus)
[docs]    def setMethod(self, theory=None):
        """
        Set the current theory value.
        :param theory: The current theory value to be set.
        :type theory: str or None
        """
        self._pop_up.setMethod(theory)
[docs]    def popUpUpdated(self):
        """
        Update the line edit text based on the current pop up selection.
        """
        theory = self._pop_up.getMethod()
        if theory is not None:
            self.setText(theory)
[docs]    def applySettings(self, settings):
        """
        Apply the specified filter settings to the pop up.
        :param settings: Filter settings to apply
        :type settings: dict
        """
        self._pop_up.applySettings(settings)