Source code for schrodinger.application.matsci.jagwidgets

"""
Contains widgets for MatSci jaguar-related panels.

Copyright Schrodinger, LLC. All rights reserved.
"""
from schrodinger.application.jaguar import basis as jag_basis
from schrodinger.application.jaguar.gui import basis_selector
from schrodinger.application.jaguar.gui import theory_selector
from schrodinger.application.jaguar.gui.tabs import optimization_tab
from schrodinger.application.jaguar.gui.tabs import scf_tab
from schrodinger.application.matsci import jaguarworkflows
from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt.appframework2 import af2
from schrodinger.ui.qt import messagebox
from schrodinger.ui.qt import swidgets


[docs]class SelectorWithPopUp(swidgets.SFrame): """ A frame that contains a label, read-only line edit, and a tool button that shows a pop up list. Requires a TOOL_BUTTON_CLASS to be defined in children. """
[docs] def __init__(self, label, default_selection, layout=None): """ Create a SelectorWithPopUp instance :param str label: The label for the edit :param str default_selection: The default selection for the edit and list :param QLayout layout: The layout to place the frame in """ super().__init__(layout=layout, layout_type=swidgets.HORIZONTAL) self.selection_le = swidgets.SLabeledEdit( label, layout=self.mylayout, read_only=True, stretch=False) self.tool_btn = self.TOOL_BUTTON_CLASS(self) self.tool_btn.setPopupValign(self.tool_btn.ALIGN_BOTTOM) self.tool_btn.popUpClosing.connect(self.selectionChanged) self.mylayout.addWidget(self.tool_btn) self.mylayout.addStretch() self.default_selection = default_selection self.reset()
[docs] def getSelection(self): """ Get the current selection :rtype: str :return: The current selection """ return self.selection_le.text()
[docs] def clearFilters(self): """ Clear the filters and make all options visible """ self.tool_btn.clearFilters()
[docs] def selectionChanged(self): """ Callback for when a new item is selected in the pop up list """ raise NotImplementedError
[docs]class BasisSetSelector(SelectorWithPopUp): """ A frame that allows the user to specify a basis from a pop up list """ TOOL_BUTTON_CLASS = basis_selector.BasisSelectorFilterListToolButton
[docs] def selectionChanged(self): """ Set the line edit to a newly selected basis set """ self.selection_le.setText(str(self.tool_btn.getBasis()))
[docs] def setBasis(self, basis): """ Set the basis for the widget :param str basis: The basis to set :raise ValueError: If basis is invalid """ self.tool_btn.setBasis(basis) # Use the same capitalization as the list widget (MATSCI-10004) self.selection_le.setText(self.tool_btn.getBasis())
[docs] def reset(self): """ Reset the widget """ self.setBasis(self.default_selection)
[docs] def setStructure(self, struct): """ Set the structure to determine valid basis sets :param `structure.Structure` struct: The structure to set """ self.tool_btn.setStructure(struct)
@af2.validator() def validate(self): """ Check if the basis set is valid :rtype: (False, msg) or True :return: False and error message if something is wrong, True if everything is OK """ if not self.tool_btn.hasAcceptableInput(): return (False, 'The specified basis set is not valid for the ' 'input structure.') return True
[docs]class TheorySelector(SelectorWithPopUp): """ A frame that allows the user to specify a theory from a pop up list """ TOOL_BUTTON_CLASS = theory_selector.DftTheorySelectorFilterListToolButton
[docs] def selectionChanged(self): """ Set the line edit to the newly selected theory """ self.selection_le.setText(str(self.tool_btn.getMethod()))
[docs] def setTheory(self, theory): """ Set the theory for the widget :param str theory: The theory to set :raise ValueError: If the theory is not valid """ self.clearFilters() # MATSCI-9750 valid = self.tool_btn.setMethod(theory) if valid: # Use the same capitalization as the list widget (MATSCI-10004) self.selection_le.setText(self.tool_btn.getMethod()) else: raise ValueError(f'"{theory}" is not in the ' 'list of available theories.')
[docs] def reset(self): """ Reset the widget """ self.setTheory(self.default_selection)
[docs]class KeywordEdit(swidgets.SLabeledEdit): """ A labeled edit for displaying, editing and retrieving Jaguar keywords """
[docs] def __init__(self, label_text="", keyword_dict=None, keyword_string="", **kwargs): """ Create a KeywordEdit instance. Any unrecognized keyword arguments are passed to the SLabeledEdit class :type label_text: str :param label_text: The text of the label for the KeywordEdit. By default, there is no label. :type keyword_dict: dict :param keyword_dict: A dictionary of keyword/value pairs for the KeywordEdit to display. If both keyword_dict and keyword_string are supplied, the keyword_dict keywords appear first. :type keyword_string: str :param keyword_string: The string to display in the KeywordEdit. If both keyword_dict and keyword_string are supplied, the keyword_dict keywords appear first. """ if 'stretch' not in kwargs: kwargs['stretch'] = False swidgets.SLabeledEdit.__init__(self, label_text, **kwargs) if not label_text: self.label.hide() self.setKeywords( keyword_dict=keyword_dict, keyword_string=keyword_string) self.default_text = str(self.text()) # This moves the text cursor to the beginning of the line # of QtWidgets.QLineEdit self.home(True)
[docs] def getKeywordString(self): """ Return the keyword string in the QLineEdit :rtype: str :return: The string in the QLineEdit. No validity checking is done. """ return str(self.text().lower())
[docs] def getKeywordDict(self, keystring=None): """ Return a dictionary whose keys are keywords and values are keyword values :type keystring: str :param keystring: If provided, the keywords are taken from this string rather than the QLineEdit. The default is to take the keywords from the QLineEdit :rtype: dict :return: Dictionary of keyword/value pairs :raise ValueError: if any tokens do not match the keyword=value format """ if keystring is None: keystring = self.getKeywordString() return jaguarworkflows.keyword_string_to_dict(keystring)
[docs] def setKeywords(self, keyword_dict=None, keyword_string=""): """ Set the text of the KeywordEdit :type keyword_dict: dict :param keyword_dict: A dictionary of keyword/value pairs for the KeywordEdit to display. If both keyword_dict and keyword_string are supplied, the keyword_dict keywords appear first. :type keyword_string: str :param keyword_string: The string to display in the KeywordEdit. If both keyword_dict and keyword_string are supplied, the keyword_dict keywords appear first. """ if keyword_dict: keyword_text = ' '.join( ['%s=%s' % (x, y) for x, y in keyword_dict.items()]) else: keyword_text = "" keyword_string = keyword_string + keyword_text self.setText(keyword_string)
[docs] def validateKeywords(self, emptyok=False): """ Validate the contents to ensure they are valid Jaguar keywords. The return value of this is compatible with appframework2 validation methods - i.e. an af2 validation method can just call: return self.keyword_le.validateKeywords() :type emptyok: bool :param emptyok: Whether it is OK for the keyword input to be empty :rtype: True or (False, str) :return: True if no errors are found, otherwise a tuple containing False and an error message. """ keywords = self.getKeywordString() if not emptyok and not keywords: return (False, 'Keyword input must not be empty') return True
[docs]class JaguarOptionsDialog(swidgets.SDialog): OVERWRITE_QUESTION_KEY = 'JAGOPTS_OVERWRITE_KEYWORDS' SPIN_RESTRICTED = 'Restricted' SPIN_UNRESTRICTED = 'Unrestricted' UHF_LABELS = { mm.MMJAG_IUHF_ON: SPIN_UNRESTRICTED, mm.MMJAG_IUHF_OFF: SPIN_RESTRICTED } DEFAULT_THEORY = 'B3LYP-D3' DEFAULT_BASIS_SET = '6-31G**' NON_ANALYTICAL_SCF_ACCURACIES = [ val for val in scf_tab.ScfTab.ACCURACY_LEVELS.values() if val != scf_tab.ScfTab.FULLY_ANALYTIC_ACCURACY ] GEOPT_CONV_CRITERIA = { x: y for x, y in optimization_tab.OptimizationTab.CONVERGENCE_CRITERIA.items() if y != mm.MMJAG_IACCG_CUSTOM } OPTIMIZATION_KEYWORDS = [ mm.MMJAG_IKEY_IGEOPT, mm.MMJAG_IKEY_MAXITG, mm.MMJAG_IKEY_IACCG, mm.MMJAG_RKEY_NOPS_OPT_SWITCH ] theoryBasisChanged = QtCore.pyqtSignal(str)
[docs] def __init__(self, master, button_label='Jaguar Options...', title=None, help_topic=None, layout=None, show_optimization=True, optional_optimization=False, pass_optimization_keyword=True, show_geopt_iterations=True, show_spin_treatment=False, show_charge=True, show_multiplicity=True, keyword_validator=None, initial_keywords=None): """ Create a JaguarOptionsDialog instance :param QWidget master: The parent widget :param str button_label: The label for the button in the panel :param str title: The dialog title. If not provided, a title will be created from the panel's title :param str help_topic: The help topic for this dialog. If not provided, an id will be created from the panel's id :param QLayout layout: The layout to add the panel widgets to :param bool show_optimization: Whether geometry optimization group should be shown :param bool optional_optimization: Whether geometry optimization is optional :param bool pass_optimization_keyword: Whether geometry optimization keyword (igeopt) should be returned when getting current keywords :param bool show_geopt_iterations: Whether maximum iterations for geometry optimization should be shown :param bool show_spin_treatment: Whether spin treatment rbg should be shown :param bool show_charge: Whether charge spinbox should be shown :param bool show_multiplicity: Whether multiplicity spinbox should be shown :param callable keyword_validator: Optional function to call to validate the keywords. Should raise KeyError if there are any issues with the keywords. :type initial_keywords: str or dict :param initial_keywords: The initial keywords for the dialog """ self.show_optimization = show_optimization self.optional_optimization = optional_optimization self.pass_optimization_keyword = pass_optimization_keyword self.show_geopt_iterations = show_geopt_iterations self.show_spin_treatment = show_spin_treatment self.show_charge = show_charge self.show_multiplicity = show_multiplicity self.keyword_validator = keyword_validator # Create the button and label in the panel. The label should be created # before layOut is called hlayout = swidgets.SHBoxLayout(layout=layout) self.panel_edit_btn = swidgets.SPushButton( button_label, layout=hlayout, command=self.showForEdit) self.panel_lbl = swidgets.SLabel('', layout=hlayout) hlayout.addStretch() if initial_keywords is None: initial_keywords = {} self.initial_keywords = self.makeDict(initial_keywords) if title is None: title = 'Jaguar Options - ' + master.title if help_topic is None: help_topic = master.help_topic + '_JAGUAR_OPTIONS' dbb = QtWidgets.QDialogButtonBox buttons = [dbb.Ok, dbb.Cancel] super().__init__( master, standard_buttons=buttons, title=title, help_topic=help_topic) self.setWindowModality(QtCore.Qt.WindowModal)
[docs] def layOut(self): """ Lay out the widgets for the dialog """ layout = self.mylayout self.theory_selector = TheorySelector( 'Theory:', self.DEFAULT_THEORY, layout=layout) self.basis_selector = BasisSetSelector( 'Basis set:', self.DEFAULT_BASIS_SET, layout=layout) for selector in (self.theory_selector, self.basis_selector): selector.selection_le.textChanged.connect(self.updatePanelLabel) self.scf_gb = swidgets.SGroupBox('SCF', parent_layout=layout) if self.show_spin_treatment: self.spin_treatment_rbg = swidgets.SLabeledRadioButtonGroup( group_label="Spin treatment:", labels=self.UHF_LABELS.values(), layout=self.scf_gb.layout) self.scf_accuracy_combo = swidgets.SLabeledComboBox( 'Accuracy level:', itemdict=scf_tab.ScfTab.ACCURACY_LEVELS, layout=self.scf_gb.layout) self.scf_iterations_sb = swidgets.SLabeledSpinBox( 'Maximum iterations:', minimum=1, value=48, maximum=999999999, layout=self.scf_gb.layout ) # Default, min and max are from scf_tab.ui if self.show_optimization: self.geopt_gb = swidgets.SGroupBox( 'Geometry optimization', parent_layout=layout, checkable=self.optional_optimization) if self.show_geopt_iterations: self.geopt_iterations_sb = swidgets.SLabeledSpinBox( 'Maximum steps:', value=100, maximum=999999999, layout=self.geopt_gb.layout) self.use_nops_cb = swidgets.SCheckBox( 'Switch to analytic integrals near convergence', layout=self.geopt_gb.layout) self.geopt_accuracy_combo = swidgets.SLabeledComboBox( 'Convergence criteria:', itemdict=self.GEOPT_CONV_CRITERIA, layout=self.geopt_gb.layout) self.no_fail_cb = swidgets.SCheckBox( 'Use special measures to prevent convergence failure', checked=False, layout=layout) if self.show_charge: self.charge_sb = swidgets.SLabeledSpinBox( 'Charge:', value=0, minimum=-99, maximum=99, layout=layout) if self.show_multiplicity: self.multiplicity_sb = swidgets.SLabeledSpinBox( 'Multiplicity:', value=1, minimum=1, maximum=99, layout=layout) self.symmetry_cb = swidgets.SCheckBox( 'Use symmetry', layout=layout, checked=True, disabled_checkstate=False) # Disable the symmetry checkbox if the panel sets the keyword to 0 symm_keyword = self.initial_keywords.get(mm.MMJAG_IKEY_ISYMM, mm.MMJAG_ISYMM_FULL) self.symmetry_cb.setEnabled(int(symm_keyword) != mm.MMJAG_ISYMM_OFF) self.additional_keywords_le = KeywordEdit( 'Additional keywords:', layout=layout, stretch=False) self.reset() # Update widgets with initial keywords validation_results = self.validate() # Validate initial keywords if validation_results.message: self.error(validation_results.message)
[docs] def updateWidgets(self, keyword_dict): """ Update the widgets to match the current keywords :param dict keyword_dict: The keywords to update widgets with :rtype: bool :return: True if everything was OK, False if something went wrong """ additional_keywords = {} for key, val in keyword_dict.items(): key = key.lower() if key == mm.MMJAG_SKEY_DFTNAME: try: self.theory_selector.setTheory(val) except ValueError: self.error(f'"{val}" is not a valid theory.') return False elif key == mm.MMJAG_SKEY_BASIS: try: self.basis_selector.setBasis(val) except ValueError: self.error(f'"{val}" is not a valid basis set.') return False elif (key == mm.MMJAG_IKEY_IUHF and self.show_spin_treatment and int(val) in self.UHF_LABELS): self.spin_treatment_rbg.setTextChecked( self.UHF_LABELS[int(val)]) elif key == mm.MMJAG_IKEY_NOPS and int(val) in (mm.MMJAG_NOPS_ON, mm.MMJAG_NOPS_OFF): # Set "Fully analytic" accuracy if the value is ON, or set # default (non-analytical) accuracy if it is OFF analytic = scf_tab.ScfTab.FULLY_ANALYTIC_ACCURACY if int(val) == mm.MMJAG_NOPS_ON: self.scf_accuracy_combo.setCurrentData(analytic) elif self.scf_accuracy_combo.currentData() == analytic: self.scf_accuracy_combo.reset() elif (key == mm.MMJAG_IKEY_IACC and int(val) in self.NON_ANALYTICAL_SCF_ACCURACIES): self.scf_accuracy_combo.setCurrentData(int(val)) elif key == mm.MMJAG_IKEY_MAXIT: self.scf_iterations_sb.setValue(int(val)) elif (key == mm.MMJAG_IKEY_IGEOPT and self.show_optimization and int(val) in (mm.MMJAG_IGEOPT_MIN, mm.MMJAG_IGEOPT_OFF)): if self.optional_optimization: self.geopt_gb.setChecked(int(val) == mm.MMJAG_IGEOPT_MIN) elif (key == mm.MMJAG_IKEY_MAXITG and self.show_optimization and self.show_geopt_iterations): self.geopt_iterations_sb.setValue(int(val)) elif key == mm.MMJAG_RKEY_NOPS_OPT_SWITCH and self.show_optimization: # Note: The dialog does not retain custom NOPS_OPT_SWITCH values self.use_nops_cb.setChecked((float(val) > 1)) elif (key == mm.MMJAG_IKEY_IACCG and self.show_optimization and int(val) in self.GEOPT_CONV_CRITERIA.values()): self.geopt_accuracy_combo.setCurrentData(int(val)) elif key == mm.MMJAG_IKEY_NOFAIL and int(val) in (0, 1): self.no_fail_cb.setChecked(int(val)) elif key == mm.MMJAG_IKEY_MOLCHG and self.show_charge: self.charge_sb.setValue(int(val)) elif key == mm.MMJAG_IKEY_MULTIP and self.show_multiplicity: self.multiplicity_sb.setValue(int(val)) elif key == mm.MMJAG_IKEY_ISYMM and int(val) in ( mm.MMJAG_ISYMM_FULL, mm.MMJAG_ISYMM_OFF): self.symmetry_cb.setChecked(int(val) == mm.MMJAG_ISYMM_FULL) else: additional_keywords[key] = val self.additional_keywords_le.setKeywords( keyword_dict=additional_keywords) return True
def _getWidgetKeywords(self, add_additional=True): """ Get the current widget keywords :param bool add_additional: Whether additional keywords should also be added :rtype: dict :return: The widget keywords """ # Theory and basis keywords = { mm.MMJAG_SKEY_DFTNAME: self.theory_selector.getSelection(), mm.MMJAG_SKEY_BASIS: self.basis_selector.getSelection() } # SCF if self.show_spin_treatment: if self.spin_treatment_rbg.checkedText() == self.SPIN_UNRESTRICTED: keywords[mm.MMJAG_IKEY_IUHF] = mm.MMJAG_IUHF_ON else: keywords[mm.MMJAG_IKEY_IUHF] = mm.MMJAG_IUHF_OFF accuracy = self.scf_accuracy_combo.currentData() if accuracy == scf_tab.ScfTab.FULLY_ANALYTIC_ACCURACY: keywords[mm.MMJAG_IKEY_NOPS] = mm.MMJAG_NOPS_ON else: keywords[mm.MMJAG_IKEY_NOPS] = mm.MMJAG_NOPS_OFF keywords[mm.MMJAG_IKEY_IACC] = accuracy keywords[mm.MMJAG_IKEY_MAXIT] = self.scf_iterations_sb.value() # Geometry optimization if self.show_optimization and (not self.optional_optimization or self.geopt_gb.isChecked()): if self.pass_optimization_keyword: keywords[mm.MMJAG_IKEY_IGEOPT] = mm.MMJAG_IGEOPT_MIN if self.show_geopt_iterations: keywords[ mm.MMJAG_IKEY_MAXITG] = self.geopt_iterations_sb.value() if self.use_nops_cb.isChecked(): keywords[mm.MMJAG_RKEY_NOPS_OPT_SWITCH] = \ optimization_tab.INITIAL_NOPS_VAL keywords[mm.MMJAG_IKEY_IACCG] = \ self.geopt_accuracy_combo.currentData() keywords[mm.MMJAG_IKEY_NOFAIL] = int(self.no_fail_cb.isChecked()) if self.show_charge: keywords[mm.MMJAG_IKEY_MOLCHG] = self.charge_sb.value() if self.show_multiplicity: keywords[mm.MMJAG_IKEY_MULTIP] = self.multiplicity_sb.value() if self.symmetry_cb.isChecked(): keywords[mm.MMJAG_IKEY_ISYMM] = mm.MMJAG_ISYMM_FULL else: keywords[mm.MMJAG_IKEY_ISYMM] = mm.MMJAG_ISYMM_OFF if add_additional: keywords.update(self.additional_keywords_le.getKeywordDict()) return keywords
[docs] def setButtonEnabled(self, state): """ Set the enabled state of the edit button in the panel :param bool state: Whether the button should be enabled """ self.panel_edit_btn.setEnabled(state)
[docs] def updatePanelLabel(self): """ Update the label in the panel with the new theory and basis """ new_text = (self.theory_selector.getSelection() + '/' + self.basis_selector.getSelection()) self.panel_lbl.setText(new_text) self.theoryBasisChanged.emit(new_text)
[docs] def showForEdit(self): """ Show the dialog """ self.show() self.raise_()
[docs] def setKeywords(self, keywords): """ Set the keywords for the dialog. Widgets that are not modified by the passed keywords will be reset to default. :type keywords: str or dict :param keywords: The keywords to set """ self.reset(keywords=self.makeDict(keywords))
[docs] def getKeywordDict(self): """ Get current keywords as a dict :rtype: dict :return: The current keywords as dict """ return self.current_keywords
[docs] def getKeywordString(self): """ Get current keywords as a string :rtype: str :return: The current keywords as a string """ return self.makeString(self.current_keywords)
[docs] def accept(self): """ Update the keywords and close the dialog if the inputs are valid """ validation_result = self.validate() if validation_result.message: self.error(validation_result.message) elif validation_result: self.current_keywords = self._getWidgetKeywords() super().accept()
[docs] def reject(self): """ Close the dialog and reset it back to when it was opened """ super().reject() self.reset(keywords=self.current_keywords)
[docs] def reset(self, keywords=None): """ Reset the dialog and update the widgets with any passed keywords :type keywords: dict or None :param keywords: The keywords to update widgets with """ self.theory_selector.reset() self.basis_selector.reset() if self.show_spin_treatment: self.spin_treatment_rbg.reset() self.scf_accuracy_combo.reset() self.scf_iterations_sb.reset() if self.show_optimization: if self.optional_optimization: self.geopt_gb.reset() if self.show_geopt_iterations: self.geopt_iterations_sb.reset() self.use_nops_cb.reset() self.geopt_accuracy_combo.reset() self.no_fail_cb.reset() if self.show_charge: self.charge_sb.reset() if self.show_multiplicity: self.multiplicity_sb.reset() self.symmetry_cb.reset() self.additional_keywords_le.reset() if keywords is None: keywords = self.initial_keywords self.current_keywords = keywords self.updateWidgets(keywords)
@af2.validator() def validate(self): """ Validate the dialog keywords :rtype: bool or (bool, str) :return: True if everything is OK, (False, msg) if the state is invalid and an error should be shown to the user in a warning dialog. """ # Make sure the keyword syntax is valid try: additional_keywords = self.additional_keywords_le.getKeywordDict() except ValueError as err: return False, str(err) # Make sure all additional keywords and values are valid for key, val in additional_keywords.items(): if hasattr(mm, 'MMJAG_IKEY_' + key.upper()): try: int(val) except ValueError: return False, (f'The "{key}" keyword ' 'requires an integer value.') elif hasattr(mm, 'MMJAG_RKEY_' + key.upper()): try: float(val) except ValueError: return False, (f'The "{key}" keyword requires' ' a floating point value.') elif not hasattr(mm, 'MMJAG_SKEY_' + key.upper()): pass # Cannot rely on the existing variables to see which keywords # exist (MATSCI-10453) # return False, f'"{key}" is not a Jaguar keyword.' # yapf: disable # Verify that all additional keywords are allowed optional_widget_keywords = ( (self.show_optimization, self.OPTIMIZATION_KEYWORDS), (self.pass_optimization_keyword, [mm.MMJAG_IKEY_IGEOPT]), (self.show_geopt_iterations, [mm.MMJAG_IKEY_MAXITG]), (self.show_charge, [mm.MMJAG_IKEY_MOLCHG]), (self.show_multiplicity, [mm.MMJAG_IKEY_MULTIP]), (self.show_spin_treatment, [mm.MMJAG_IKEY_IUHF]) ) # yapf: enable for is_shown, keywords in optional_widget_keywords: if not is_shown: for keyword in keywords: if keyword in additional_keywords: return False, (f'The "{keyword}" keyword may ' 'not be used with this panel.') # Put any keywords supported by the widgets into them if not self.updateWidgets(additional_keywords): return False widget_keywords = self._getWidgetKeywords(add_additional=False) additional_keywords = self.additional_keywords_le.getKeywordDict() # Validate keywords using the custom validator, if any if self.keyword_validator: all_keywords = dict(widget_keywords) all_keywords.update(additional_keywords) try: self.keyword_validator(all_keywords) except KeyError as msg: return False, str(msg) # Check if any of the additional keywords is overwriting widget keywords overwrite = [k for k in additional_keywords if k in widget_keywords] if overwrite: msg = ( 'The following additional keywords will overwrite ' 'existing keywords:\n' + '\n'.join(overwrite) + '\n\nContinue?') if not messagebox.show_question( parent=self, text=msg, title='Overwrite keywords?', save_response_key=self.OVERWRITE_QUESTION_KEY): return False return True @af2.validator() def validateBasisSet(self, structs): """ Validate that the passed structures are compatible with the current basis set :param iterable structs: The structures to validate :rtype: bool or (bool, str) :return: True if everything is OK, (False, msg) if the state is invalid and an error should be shown to the user. """ basis_name = self.basis_selector.getSelection() for struct in structs: num_funcs = jag_basis.num_functions_all_atoms(basis_name, struct)[0] if num_funcs == 0: error = (f'The "{basis_name}" basis set is not valid' f' for the "{struct.title}" structure.') return False, error return True
[docs] @staticmethod def makeDict(keywords): """ Create a keyword dictionary if the passed keywords are in string format :type keywords: str or dict :param keywords: Keyword dict or string :rtype: dict :return: Keyword dictionary """ if isinstance(keywords, str): keywords = jaguarworkflows.keyword_string_to_dict(keywords) return keywords
[docs] @staticmethod def makeString(keywords): """ Create a keyword string if the passed keywords are in dictionary format :type keywords: str or dict :param keywords: Keyword dict or string :rtype: str :return: Keyword string """ if isinstance(keywords, dict): keywords = jaguarworkflows.keyword_dict_to_string(keywords) return keywords
[docs] def assertInKeywords(self, keywords): """ Assert that the passed keywords are a subset of the dialog's keywords Used for unittests. :type keywords: str or dict :param keywords: Keyword dict or string """ for key, val in self.makeDict(keywords).items(): assert key in self.current_keywords, (f'{key} is not in ' f'{self.current_keywords}') actual_val = self.current_keywords[key] if isinstance(actual_val, str) and isinstance(val, str): actual_val = actual_val.lower() val = val.lower() assert val == actual_val, f'{val} != {actual_val}'