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

import math
import sys
import warnings
from collections import OrderedDict

import numpy

from schrodinger.infra import mm
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt.QtCore import Qt

from .. import ui
from .. import utils as gui_utils
from ..utils import JaguarSettingWarning
from ..utils import SpinTreatment
from .base_tab import BaseTab

# This is used for floating point comparisons
EPSILON = 1e-10

# Definitions of property rows
NUM_ROWS = 14
(VIBRATIONAL_ROW, SURFACES_ROW, ESP_ROW, MULLIKEN_ROW, NBO_ROW, MULTIPOLE_ROW,
 POLARIZABILITY_ROW, NMR_ROW, FUKUI_ROW, STOCKHOLDER_ROW, VCD_ROW, ECD_ROW,
 RAMAN_ROW, MOSSBAUER_ROW) = list(range(NUM_ROWS))

# Map of properties that don't have options. Key is the row id
# and value is a tuple of 3 mmjag keywords: ikey, on and off.
NO_OPTIONS_PROPS = {
    NBO_ROW: (mm.MMJAG_IKEY_NBO, mm.MMJAG_NBO_ON, mm.MMJAG_NBO_OFF),
    NMR_ROW: (mm.MMJAG_IKEY_NMR, mm.MMJAG_NMR_ON, mm.MMJAG_NMR_OFF),
    FUKUI_ROW: (mm.MMJAG_IKEY_FUKUI, mm.MMJAG_FUKUI_ON, mm.MMJAG_FUKUI_OFF),
    STOCKHOLDER_ROW: (mm.MMJAG_IKEY_STOCKHOLDER_Q, mm.MMJAG_STOCKHOLDER_Q_ON,
                      mm.MMJAG_STOCKHOLDER_Q_OFF),
    VCD_ROW: (mm.MMJAG_IKEY_IVCD, mm.MMJAG_IVCD_ON, mm.MMJAG_IVCD_OFF),
    ECD_ROW: (mm.MMJAG_IKEY_ECD, mm.MMJAG_ECD_ON, mm.MMJAG_ECD_OFF)
}


[docs]class BaseSubTab(QtCore.QObject): """ Base class for all properties tab subtabs. Subtabs that contain options should be subclassed. Subclasses should redefine getMmJagKeywords and loadSettings functions. Function theoryUpdated need to be redefined only for sub tabs that depend on theory options. :ivar theory_level: current theory level :vartype theory_level: str :ivar spin: current state of 'spin unrestricted' toggle in theory tab :vartype spin: bool :ivar row_id: sub tab id a.k.a. property row number it is associated with :vartype row_id: int :ivar ui: And object that provides access to all subtab widgets :vartype ui: `QWidget` """
[docs] def __init__(self, ui, row_id=None): """ Sub tab initializer. """ super(BaseSubTab, self).__init__() self.ui = ui # theory settings self.theory_level = None self.spin = None self.row_id = row_id self.setup()
[docs] def setup(self): """ This function is called to customize sub tab behavior. It should be reimplemented for any subclass that has custom features. """
[docs] def getMmJagKeywords(self, checked): """ This function returns dictionary of mmjag keywords for this sub tab. It is only used for tabs that don't have options. :param checked: True if property row is 'checked' :type checked: bool :return: mmjag keywords dictionary :rtype: dict """ keywords = {} if self.row_id in NO_OPTIONS_PROPS: ikey, on_val, off_val = NO_OPTIONS_PROPS[self.row_id] keywords[ikey] = on_val if checked else off_val return keywords
[docs] def loadSettings(self, keywords): """ This function restores this sub tab from the keywords dictionary. :param keywords: mmjag keywrods dictionary :type keywords: dict """ item = self.ui.properties_tablew.item(self.row_id, 0) if self.row_id in NO_OPTIONS_PROPS: ikey, on_val, off_val = NO_OPTIONS_PROPS[self.row_id] if keywords[ikey] == off_val: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked)
[docs] def theoryUpdated(self, theory_level, dft_functional, spin_treatment, basis): """ Respond to the user changing the level of theory or the basis set. :param theory: The current level of theory. Must be one of "DFT", "HF", "LMP2" or "RIMP2" :type theory: str :param dft_functional: If c{theory} is "DFT", the currently selected functional level. If `theory` is not "DFT" or if the user has not specified a functional level, should be None. :type dft_functional: str or NoneType :param spin_unrestricted: Whether the spin-unrestricted check box is checked in the theory tab. If `theory` is "LMP2" or "RIMP2", should be None. :type spin_unrestricted: bool :param basis: The currently selected basis set without the polarization or diffuse levels (i.e. "6-31G" rather than "6-31G**+") :type basis: str """
[docs]class RamanSubTab(BaseSubTab):
[docs] def getMmJagKeywords(self, checked): """ This function returns dictionary of mmjag keywords for Raman Spectroscopy :param checked: True if property row is 'checked' :type checked: bool :return: mmjag keywords dictionary :rtype: dict """ keywords = {} ikey, on_val, off_val = (mm.MMJAG_IKEY_IRAMAN, mm.MMJAG_IRAMAN_ON, mm.MMJAG_IRAMAN_OFF) keywords[ikey] = on_val if checked else off_val if checked: # We only set the value affirmatively, as it can be changed # elsewhere, and we don't want to override that unless Raman is on keywords[mm.MMJAG_IKEY_IFREQ] = mm.MMJAG_IFREQ_PS return keywords
[docs] def loadSettings(self, keywords): """ This function restores this sub tab from the keywords dictionary. :param keywords: mmjag keywrods dictionary :type keywords: dict """ item = self.ui.properties_tablew.item(RAMAN_ROW, 0) ikey, on_val, off_val = (mm.MMJAG_IKEY_IRAMAN, mm.MMJAG_IRAMAN_ON, mm.MMJAG_IRAMAN_OFF) if keywords[ikey] == off_val: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked)
[docs]class VibrationalSubTab(BaseSubTab): """ This sub tab class is used for Vibrational property. :cvar VIBRATION_MASS_METHOD: An OrderedDict of {atomic masses method: mmjag keyword value} used to populate 'Atomic masses' combo box. :vartype VIBRATION_MASS_METHOD: `collections.OrderedDict` """ VIBRATION_MASS_METHOD = OrderedDict( (("Most abundant isotopes", mm.MMJAG_MASSAV_NO), ("Average isotopic masses", mm.MMJAG_MASSAV_AVG))) # Vibrational tab scaling options NUM_SCALING = 4 (VIBRATION_SCALING_NONE, VIBRATION_SCALING_PULAY, VIBRATION_SCALING_AUTO, VIBRATION_SCALING_CUSTOM) = list(range(NUM_SCALING))
[docs] def setup(self): # populate atomic masses combo box self.ui.atomic_masses_combo.addItemsFromDict(self.VIBRATION_MASS_METHOD) # FIXME: the min/max values should probably be changed to # something more reasonable in the future. self.ui.pressure_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self)) self.ui.start_temp_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self)) self.ui.increment_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self))
[docs] def getMmJagKeywords(self, checked): keywords = {} if checked: keywords.update(self._getBasicKeywords()) keywords.update(self._getScalingKeywords()) keywords.update(self._getThermochemistryKeywords()) else: keywords.update(self._getDefaultKeywords()) return keywords
def _getBasicKeywords(self): """ This function returns mmjag keywords for the basic options. """ keywords = {} ifreq = mm.MMJAG_IFREQ_PS if self.ui.use_hessian_cb.isChecked(): ifreq = mm.MMJAG_IFREQ_HESS keywords[mm.MMJAG_IKEY_IFREQ] = ifreq irder = mm.MMJAG_IRDER_OFF if self.ui.ir_intensities_cb.isChecked(): irder = mm.MMJAG_IRDER_ON keywords[mm.MMJAG_IKEY_IRDER] = irder massav = self.ui.atomic_masses_combo.currentData() keywords[mm.MMJAG_IKEY_MASSAV] = massav return keywords def _getScalingKeywords(self): """ This function returns mmjag keywords for scaling options. """ keywords = {} isqm = mm.MMJAG_ISQM_OFF scale_factor = 1.0 auto_scale = mm.MMJAG_AUTO_SCALE_OFF index = self.ui.scaling_combo.currentIndex() if index == self.VIBRATION_SCALING_PULAY: isqm = mm.MMJAG_ISQM_ON elif index == self.VIBRATION_SCALING_AUTO: auto_scale = mm.MMJAG_AUTO_SCALE_AUTO isqm = None scale_factor = None elif index == self.VIBRATION_SCALING_CUSTOM: scale_factor = self.ui.custom_scale_sb.value() keywords[mm.MMJAG_IKEY_ISQM] = isqm keywords[mm.MMJAG_RKEY_SCALFR] = scale_factor keywords[mm.MMJAG_IKEY_AUTO_SCALE] = auto_scale return keywords def _getThermochemistryKeywords(self): """ This function returns mmjag keywords for thermochemistry options. """ keywords = {} pressure = gui_utils.validate_le_float_input( self.ui.pressure_le, "Invalid input for pressure field") keywords[mm.MMJAG_RKEY_PRESS] = pressure start_temp = gui_utils.validate_le_float_input( self.ui.start_temp_le, "Invalid input for start temperature field") keywords[mm.MMJAG_RKEY_TMPINI] = start_temp temp_increment = gui_utils.validate_le_float_input( self.ui.increment_le, "Invalid input for increment field") keywords[mm.MMJAG_RKEY_TMPSTP] = temp_increment num_steps = self.ui.num_steps_sb.value() keywords[mm.MMJAG_IKEY_NTEMP] = num_steps output_units = mm.MMJAG_EUNIT_CALS if self.ui.output_kj_rb.isChecked(): output_units = mm.MMJAG_EUNIT_JOULES keywords[mm.MMJAG_IKEY_EUNIT] = output_units return keywords def _getDefaultKeywords(self): """ This function returns default keywords. """ keywords = {} keywords[mm.MMJAG_IKEY_IFREQ] = mm.MMJAG_IFREQ_OFF keywords[mm.MMJAG_IKEY_IRDER] = mm.MMJAG_IRDER_OFF keywords[mm.MMJAG_IKEY_MASSAV] = mm.MMJAG_MASSAV_NO keywords[mm.MMJAG_IKEY_ISQM] = mm.MMJAG_ISQM_OFF keywords[mm.MMJAG_RKEY_SCALFR] = None keywords[mm.MMJAG_IKEY_AUTO_SCALE] = mm.MMJAG_AUTO_SCALE_OFF keywords[mm.MMJAG_RKEY_PRESS] = None keywords[mm.MMJAG_RKEY_TMPINI] = None keywords[mm.MMJAG_RKEY_TMPSTP] = None keywords[mm.MMJAG_IKEY_NTEMP] = None keywords[mm.MMJAG_IKEY_EUNIT] = mm.MMJAG_EUNIT_CALS return keywords
[docs] def loadSettings(self, keywords): self._loadBasicSettings(keywords) self._loadScalingSettings(keywords) self._loadThermochemistrySettings(keywords)
def _loadBasicSettings(self, keywords): """ This function loads basic settings from mmjag. """ item = self.ui.properties_tablew.item(VIBRATIONAL_ROW, 0) ifreq = keywords[mm.MMJAG_IKEY_IFREQ] if ifreq == mm.MMJAG_IFREQ_OFF: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked) hessian = False if ifreq == mm.MMJAG_IFREQ_HESS: hessian = True self.ui.use_hessian_cb.setChecked(hessian) irder = (keywords[mm.MMJAG_IKEY_IRDER] != mm.MMJAG_IRDER_OFF) self.ui.ir_intensities_cb.setChecked(irder) self.ui.atomic_masses_combo.setCurrentMmJagData(keywords, mm.MMJAG_IKEY_MASSAV, "atomic masses method") def _loadScalingSettings(self, keywords): """ This function loads scaling settings from mmjag. """ isqm = keywords[mm.MMJAG_IKEY_ISQM] scale_factor = keywords[mm.MMJAG_RKEY_SCALFR] auto_scale = keywords[mm.MMJAG_IKEY_AUTO_SCALE] index = self.VIBRATION_SCALING_NONE if isqm == mm.MMJAG_ISQM_OFF: if math.fabs(scale_factor - 1.0) < EPSILON: index = self.VIBRATION_SCALING_NONE elif auto_scale == mm.MMJAG_IKEY_AUTO_SCALE: index = self.VIBRATION_SCALING_AUTO else: index = self.VIBRATION_SCALING_CUSTOM self.ui.custom_scale_sb.setValue(scale_factor) else: index = self.VIBRATION_SCALING_PULAY self.ui.scaling_combo.setCurrentIndex(index) def _loadThermochemistrySettings(self, keywords): """ This function loads thermochemistry settings from mmjag. """ # Pressures can reasonably go lower than the number of decimal places # allowed in the line edit. Use scientific notation if the requested # pressure is too small to show otherwise. pressure = keywords[mm.MMJAG_RKEY_PRESS] decimals = self.ui.pressure_le.validator().decimals() if pressure != round(pressure, decimals): # Using the numpy method vs %e avoids a long line of trailing zeros # to fill out the required decimal places strp = numpy.format_float_scientific(pressure, decimals, trim='0', exp_digits=1) else: strp = str(pressure) self.ui.pressure_le.setText(strp) start_temp = keywords[mm.MMJAG_RKEY_TMPINI] self.ui.start_temp_le.setText(str(start_temp)) temp_increment = keywords[mm.MMJAG_RKEY_TMPSTP] self.ui.increment_le.setText(str(temp_increment)) num_steps = keywords[mm.MMJAG_IKEY_NTEMP] self.ui.num_steps_sb.setValue(num_steps) output_units = keywords[mm.MMJAG_IKEY_EUNIT] if output_units == mm.MMJAG_EUNIT_CALS: self.ui.output_kcal_rb.setChecked(True) else: self.ui.output_kj_rb.setChecked(True)
[docs] def theoryUpdated(self, theory_level, dft_functional, spin_treatment, basis): """ This slot is called when basis set is changed in Molecule tab. Depending on the type of basis set it would enable or disable 'Pulay SQM' item in the scaling combo box. """ index = self.ui.scaling_combo.findText("Pulay SQM") item = self.ui.scaling_combo.model().item(index) flag = item.flags() if basis == "6-31G" and theory_level == gui_utils.THEORY_DFT and \ dft_functional == "B3LYP": flag |= QtCore.Qt.ItemIsEnabled else: flag &= ~QtCore.Qt.ItemIsEnabled item.setFlags(flag)
[docs]class SurfacesSubTab(BaseSubTab): """ This sub tab class is used for Surfaces property. :cvar ENERGY_UNIT_METHOD: An OrderedDict of {energy unit method: mmjag keyword value} used to populate 'Energy units' combo box. :vartype ENERGY_UNIT_METHOD: `collections.OrderedDict` """ SURFACES_ORBITAL_HOMOMINUS = "homo-" SURFACES_ORBITAL_LUMOPLUS = "lumo+" MOLECULAR_ORBITALS = OrderedDict((("HOMO-", SURFACES_ORBITAL_HOMOMINUS), ("LUMO+", SURFACES_ORBITAL_LUMOPLUS))) ALPHA_ORBITALS = [mm.MMJAG_SKEY_IORB1A, mm.MMJAG_SKEY_IORB2A] BETA_ORBITALS = [mm.MMJAG_SKEY_IORB1B, mm.MMJAG_SKEY_IORB2B] DEFAULT_ORBITAL = "0" ENERGY_UNIT_METHOD = OrderedDict( (("kcal/mol", mm.MMJAG_ESPUNIT_KCALMOL), ("kcal/mol/electron", mm.MMJAG_ESPUNIT_KCALMOLELEC), ("kT/electorn at 298.15K", mm.MMJAG_ESPUNIT_KTELEC), ("hartrees", mm.MMJAG_ESPUNIT_HARTREE), ("kT at 298.15K", mm.MMJAG_ESPUNIT_KT), ("eV", mm.MMJAG_ESPUNIT_EV)))
[docs] def setup(self): # define mapping between orbital settings and corresponding widgets self.orbital_widgets = { mm.MMJAG_SKEY_IORB1A: (self.ui.alpha_from_combo, self.ui.alpha_from_sb), mm.MMJAG_SKEY_IORB2A: (self.ui.alpha_to_combo, self.ui.alpha_to_sb), mm.MMJAG_SKEY_IORB1B: (self.ui.beta_from_combo, self.ui.beta_from_sb), mm.MMJAG_SKEY_IORB2B: (self.ui.beta_to_combo, self.ui.beta_to_sb) } # populate energy units combo box self.ui.surface_energy_units_combo.addItemsFromDict( self.ENERGY_UNIT_METHOD) # Set up validators self.ui.box_size_adjust_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self)) self.ui.grid_density_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self)) self.ui.noncovalent_grid_density_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self)) # define connecttions for widget in [ self.ui.electrostatic_potential_cb, self.ui.average_ionization_energy_cb ]: widget.stateChanged.connect(self._enableEnergyUnits) for widget in [self.ui.alpha_from_combo, self.ui.alpha_to_combo]: widget.addItemsFromDict(self.MOLECULAR_ORBITALS) widget.currentIndexChanged.connect(self._updateTotalAlphaOrbitals) for widget in [self.ui.alpha_from_sb, self.ui.alpha_to_sb]: widget.valueChanged.connect(self._updateTotalAlphaOrbitals) for widget in [self.ui.beta_from_combo, self.ui.beta_to_combo]: widget.addItemsFromDict(self.MOLECULAR_ORBITALS) widget.currentIndexChanged.connect(self._updateTotalBetaOrbitals) for widget in [self.ui.beta_from_sb, self.ui.beta_to_sb]: widget.valueChanged.connect(self._updateTotalBetaOrbitals) self.ui.molecular_orbitals_cb.stateChanged.connect(self._updateOrbitals)
[docs] def getMmJagKeywords(self, checked): # FIXME verify that this is correct since surfaces is the # first property that does not seem to set some flag off # when its unchecked. keywords = {} if checked: keywords.update(self._getElectrostaticKeywords()) keywords.update(self._getNoncovalentKeywords()) keywords.update(self._getElectroDensityKeywords()) keywords.update(self._getSpinDensityKeywords()) keywords.update(self._getOrbitalsKeywords()) keywords.update(self._getBoxKeywords()) else: keywords.update(self._getDefaultKeywords()) if self.ui.nto_for_tddft_cb.isChecked(): keywords[mm.MMJAG_IKEY_TDDFT_NTO] = mm.MMJAG_TDDFT_NTO_ON return keywords
def _getElectrostaticKeywords(self): """ This function gets electrostatic potential and ionization energy surface keywords. """ keywords = {} # default values iplotesp = mm.MMJAG_IPLOTESP_OFF iplotalie = mm.MMJAG_IPLOTALIE_OFF espunit = None esp = self.ui.electrostatic_potential_cb.isChecked() alie = self.ui.average_ionization_energy_cb.isChecked() if esp or alie: if esp: iplotesp = mm.MMJAG_IPLOTESP_ON if alie: iplotalie = mm.MMJAG_IPLOTALIE_ON espunit = self.ui.surface_energy_units_combo.currentData() keywords[mm.MMJAG_IKEY_IPLOTESP] = iplotesp keywords[mm.MMJAG_IKEY_IPLOTALIE] = iplotalie keywords[mm.MMJAG_IKEY_ESPUNIT] = espunit return keywords def _getNoncovalentKeywords(self): """ This function returns keywords for noncovalent surface options. """ keywords = {} iplotnoncov = mm.MMJAG_IPLOTNONCOV_OFF if self.ui.noncovalent_interactions_cb.isChecked(): iplotnoncov = mm.MMJAG_IPLOTNONCOV_ON noncov_grid = gui_utils.validate_le_float_input( self.ui.noncovalent_grid_density_le, "Invalid input for noncovalent grid density.") keywords[mm.MMJAG_RKEY_PLOTRESNONCOV] = noncov_grid keywords[mm.MMJAG_IKEY_IPLOTNONCOV] = iplotnoncov return keywords def _getElectroDensityKeywords(self): """ This function returns keywords for electron density surface options. """ keywords = {} iplotden = mm.MMJAG_IPLOTDEN_OFF if self.ui.electron_densities_cb.isChecked(): if self.ui.density_only_rb.isChecked(): iplotden = mm.MMJAG_IPLOTDEN_ON elif self.ui.density_and_diff_rb.isChecked(): iplotden = mm.MMJAG_IPLOTDEN_BOTH keywords[mm.MMJAG_IKEY_IPLOTDEN] = iplotden return keywords def _getSpinDensityKeywords(self): """ This function returns keywords for spin density options. """ keywords = {} iplotspn = mm.MMJAG_IPLOTSPN_OFF surf_cb = self.ui.properties_tablew.item(SURFACES_ROW, 0) if (surf_cb.checkState() and self.ui.spin_density_cb.isEnabled() and self.ui.spin_density_cb.isChecked()): iplotspn = mm.MMJAG_IPLOTSPN_ON keywords[mm.MMJAG_IKEY_IPLOTSPN] = iplotspn return keywords def _getOrbitalsKeywords(self): """ This function returns keywords for molecular orbitals options. """ keywords = { mm.MMJAG_SKEY_IORB1A: self.DEFAULT_ORBITAL, mm.MMJAG_SKEY_IORB1B: self.DEFAULT_ORBITAL, mm.MMJAG_SKEY_IORB2A: self.DEFAULT_ORBITAL, mm.MMJAG_SKEY_IORB2B: self.DEFAULT_ORBITAL } if not self.ui.molecular_orbitals_cb.isChecked(): return keywords if self.ui.alpha_frame.isEnabled(): iorb = self._getOrbitals(self.ALPHA_ORBITALS) keywords.update(iorb) if self.ui.beta_frame.isEnabled(): iorb = self._getOrbitals(self.BETA_ORBITALS) keywords.update(iorb) return keywords def _getBoxKeywords(self): """ This function returns keywords for box size and grid density options. """ keywords = {} box_size_adjust = gui_utils.validate_le_float_input( self.ui.box_size_adjust_le, "Invalid input for box size adjustment field.") for key in [mm.MMJAG_RKEY_XADJ, mm.MMJAG_RKEY_YADJ, mm.MMJAG_RKEY_ZADJ]: keywords[key] = box_size_adjust grid_density = gui_utils.validate_le_float_input( self.ui.grid_density_le, "Invalid input for grid density field.") keywords[mm.MMJAG_RKEY_PLOTRES] = grid_density return keywords def _getDefaultKeywords(self): """ This function returns default keywords. """ keywords = {} keywords[mm.MMJAG_IKEY_IPLOTESP] = mm.MMJAG_IPLOTESP_OFF keywords[mm.MMJAG_IKEY_IPLOTALIE] = mm.MMJAG_IPLOTALIE_OFF keywords[mm.MMJAG_IKEY_ESPUNIT] = None keywords[mm.MMJAG_IKEY_IPLOTNONCOV] = mm.MMJAG_IPLOTNONCOV_OFF keywords[mm.MMJAG_RKEY_PLOTRESNONCOV] = None keywords[mm.MMJAG_IKEY_IPLOTDEN] = mm.MMJAG_IPLOTDEN_OFF keywords[mm.MMJAG_IKEY_IPLOTSPN] = mm.MMJAG_IPLOTSPN_OFF for key in [mm.MMJAG_RKEY_XADJ, mm.MMJAG_RKEY_YADJ, mm.MMJAG_RKEY_ZADJ]: keywords[key] = None keywords[mm.MMJAG_RKEY_PLOTRES] = None keywords[mm.MMJAG_SKEY_IORB1A] = "0" keywords[mm.MMJAG_SKEY_IORB1B] = "0" keywords[mm.MMJAG_SKEY_IORB2A] = "0" keywords[mm.MMJAG_SKEY_IORB2B] = "0" return keywords
[docs] def loadSettings(self, keywords): do_surfaces = self._loadSurfaceSettings(keywords) if self._loadOrbitalsSettings(keywords): do_surfaces = True # check surfaces row if needed. item = self.ui.properties_tablew.item(SURFACES_ROW, 0) check_state = QtCore.Qt.Unchecked if do_surfaces: check_state = QtCore.Qt.Checked item.setCheckState(check_state) self.ui.surface_energy_units_combo.setCurrentMmJagData( keywords, mm.MMJAG_IKEY_ESPUNIT, "energy units method") box_size_adjust = keywords[mm.MMJAG_RKEY_XADJ] self.ui.box_size_adjust_le.setText(str(box_size_adjust)) grid_density = keywords[mm.MMJAG_RKEY_PLOTRES] self.ui.grid_density_le.setText(str(grid_density))
def _loadSurfaceSettings(self, keywords): """ This function determines surfaces settings from mmjag keywords. If any of the surface calculations are on return True, otherwise return False. :param keywords: mmjag keywords dictionary :type keywords: dict :return: True if at least one surface calculation is 'checked'. :rtype: bool """ do_surfaces = False do_esp = (keywords[mm.MMJAG_IKEY_IPLOTESP] != mm.MMJAG_IPLOTESP_OFF) self.ui.electrostatic_potential_cb.setChecked(do_esp) do_alie = (keywords[mm.MMJAG_IKEY_IPLOTALIE] != mm.MMJAG_IPLOTALIE_OFF) self.ui.average_ionization_energy_cb.setChecked(do_alie) do_noncov = \ (keywords[mm.MMJAG_IKEY_IPLOTNONCOV] != mm.MMJAG_IPLOTNONCOV_OFF) self.ui.noncovalent_interactions_cb.setChecked(do_noncov) noncov_grid = keywords[mm.MMJAG_RKEY_PLOTRESNONCOV] self.ui.noncovalent_grid_density_le.setText(str(noncov_grid)) iplotden = keywords[mm.MMJAG_IKEY_IPLOTDEN] if iplotden == mm.MMJAG_IPLOTDEN_OFF: do_den = False elif iplotden == mm.MMJAG_IPLOTDEN_ON: do_den = True self.ui.density_only_rb.setChecked(True) elif iplotden == mm.MMJAG_IPLOTDEN_BOTH: do_den = True self.ui.density_and_diff_rb.setChecked(True) self.ui.electron_densities_cb.setChecked(do_den) do_spin = (keywords[mm.MMJAG_IKEY_IPLOTSPN] != mm.MMJAG_IPLOTSPN_OFF) self.ui.spin_density_cb.setChecked(do_spin) if do_esp or do_alie or do_noncov or do_den or do_spin: do_surfaces = True return do_surfaces def _loadOrbitalsSettings(self, keywords): """ This function loads orbitals settings from mmjag keywords. :param keywords: mmjag keywrods dictionary :type keywords: dict :return: False if all orbitals have default value of DEFAULT_ORBITAL. True otherwise. :rtype: bool """ check_orbitals = False all_orbitals = self.ALPHA_ORBITALS + self.BETA_ORBITALS for iorb in all_orbitals: v = keywords[iorb] is_default = self._setOrbital(iorb, v) if not is_default: check_orbitals = True self.ui.molecular_orbitals_cb.setChecked(check_orbitals) return check_orbitals
[docs] def theoryUpdated(self, theory_level, dft_functional, spin_treatment, basis): """ This sub tab needs to be updated when theory level or 'spin unrestricted' toggle state are changed. """ self.theory_level = theory_level self.spin = spin_treatment.unrestrictedAvailable() self.ui.spin_density_cb.setEnabled(self.spin) self._updateOrbitals()
def _updateOrbitals(self): """ This function checks surfaces tabs widget states and theory settings and enables various molecular orbitals widgets as needed. """ checked = self.ui.molecular_orbitals_cb.isChecked() self.ui.alpha_frame.setEnabled(checked) self.ui.beta_frame.setEnabled(checked) if not checked: return # orbitals frames are enabled, so check theory settings and # determine whether beta orbitals options should be shown. show_beta = True if self.theory_level == gui_utils.THEORY_DFT or \ self.theory_level == gui_utils.THEORY_HF: show_beta = bool(self.spin) self.ui.beta_frame.setEnabled(show_beta) def _enableEnergyUnits(self): """ This function is used to check state of 'Electrostatic potential' and 'Average local ionization energy' check boxes to determine whether 'Energy units' combo box should be enabled. """ checked = False if self.ui.electrostatic_potential_cb.isChecked() or \ self.ui.average_ionization_energy_cb.isChecked(): checked = True self.ui.surface_energy_units_combo.setEnabled(checked) def _updateTotalAlphaOrbitals(self): """ This function calculates total number of 'alpha' orbitals. """ start_base = self.ui.alpha_from_combo.currentData() start = self.ui.alpha_from_sb.value() end_base = self.ui.alpha_to_combo.currentData() end = self.ui.alpha_to_sb.value() txt = self._totalOrbitalsLabel(start_base, start, end_base, end) self.ui.alpha_lbl.setText(txt) def _updateTotalBetaOrbitals(self): """ This function calculates total number of 'alpha' orbitals. """ start_base = self.ui.beta_from_combo.currentData() start = self.ui.beta_from_sb.value() end_base = self.ui.beta_to_combo.currentData() end = self.ui.beta_to_sb.value() txt = self._totalOrbitalsLabel(start_base, start, end_base, end) self.ui.beta_lbl.setText(txt) def _totalOrbitalsLabel(self, start_base, start, end_base, end): """ Calculate total number of orbitals and return text to display in the label. :param start_base: start orbitals base (HOMOMINUS or LUMOPLUS) :type start_base: int :param start: start orbitals index :type start: int :param end_base: end orbitals base (HOMOMINUS or LUMOPLUS) :type end_base: int :param end: end orbitals index :type end: int :return: text of label to show total number of orbitals :rtype: str """ orbital_offset_increment = 1 if start_base != end_base: orbital_offset_increment = 2 if start_base == self.SURFACES_ORBITAL_HOMOMINUS: start = -start if end_base == self.SURFACES_ORBITAL_HOMOMINUS: end = -end num_orbitals = abs(end - start) + orbital_offset_increment txt = "Total number of orbitals: %d" % num_orbitals return txt def _getOrbitals(self, orbitals): """ This function returns keywords for the given list of orbitals. :param orbitals: list of orbitals that contains mmjag keywords for iorb1a, iorb2a etc. :type orbitals: list :return: dictionary of orbital setting, which has mmjag orbitals as keys and values that have format `<homo-|lumo+>N`. :rtype: dict """ keywords = {} for iorb in orbitals: base_widget, count_widget = self.orbital_widgets[iorb] base = base_widget.currentData() count = count_widget.value() v = "%s%d" % (base, count) keywords[iorb] = v return keywords def _setOrbital(self, iorb, value): """ This function sets widgets corresponding to a given molecular orbital. Orbital's base and count are parsed from a given value argument, which has format `<homo-|lumo+>N`. :param iorb: molecular orbital specified by mmjag keywords, such as iorb1a, iorb2a, iorb1b or iorb2b. :type iorb: string :param value: orbital value obtained from mmjag keywords :type value: string :return: True if default orbital value was found and False otherwise :rtype: bool """ # check for default value if value == self.DEFAULT_ORBITAL: self._setOrbitalDefault(iorb) return True else: base, count = self._parseOrbitalValue(iorb, value) base_widget, count_widget = self.orbital_widgets[iorb] base_widget.setCurrentData(base) count_widget.setValue(count) return False def _setOrbitalDefault(self, iorb): """ Set orbital widgets to default values. If 'from' orbital it will be homo-0 and if 'to' orbital it will be lumo+0. :param iorb: molecular orbital specified by mmjag keywords, such as iorb1a, iorb2a, iorb1b or iorb2b. :type iorb: string """ base_widget, count_widget = self.orbital_widgets[iorb] count_widget.setValue(0) if iorb in [mm.MMJAG_SKEY_IORB1A, mm.MMJAG_SKEY_IORB1B]: base_widget.setCurrentData(self.SURFACES_ORBITAL_HOMOMINUS) else: base_widget.setCurrentData(self.SURFACES_ORBITAL_LUMOPLUS) self.ui.nto_for_tddft_cb.setChecked(False) self.ui.nto_for_tddft_cb.setEnabled(False) def _parseOrbitalValue(self, iorb, value): """ This function splits given orbital value into base ('homo-' or 'lumo+') and an orbital count. It then returns a (base, count) tuple. An exception is raised if value does not have format `<homo-|lumo+>N`. :param iorb: molecular orbital specified by mmjag keywords, such as iorb1a, iorb2a, iorb1b or iorb2b. :type iorb: string :param value: mmjag orbital value :type value: string :return: tuple that contains orbital's base and count :rtype: tuple """ msg = ("Molecular orbital value %s for keyword %s does not " "have correct format. It should start with homo- or " "lumo+ followed by the integer number." % (value, iorb)) try: base = value[0:5] if base not in [ self.SURFACES_ORBITAL_HOMOMINUS, self.SURFACES_ORBITAL_LUMOPLUS ]: warnings.warn(JaguarSettingWarning(msg)) return "", 0 count = int(value[5:]) except ValueError: warnings.warn(JaguarSettingWarning(msg)) return "", 0 return base, count
[docs] def getSettingsAffectedBySpinTreatment(self): """ Get all mmjag values that need to be set on a per-structure basis dependent on the spin treatment and the spin multiplicity. :return: A tuple of: - A dictionary of {keyword: value} for all settings required for an unrestricted spin treatment - A dictionary of {keyword: value} for all settings required for a restricted spin treatment :rtype: tuple """ unres_keywords = self._getSpinDensityKeywords() res_keywords = {mm.MMJAG_IKEY_IPLOTSPN: mm.MMJAG_IPLOTSPN_OFF} surf_cb = self.ui.properties_tablew.item(SURFACES_ROW, 0) if (surf_cb.checkState() and self.ui.molecular_orbitals_cb.isChecked() and self.ui.beta_frame.isEnabled()): unres_keywords.update(self._getOrbitals(self.BETA_ORBITALS)) res_keywords.update( {orb: self.DEFAULT_ORBITAL for orb in self.BETA_ORBITALS}) return unres_keywords, res_keywords
[docs]class ESPSubTab(BaseSubTab): """ This sub tab class is used for ESP property. :cvar FIT_ESP_METHOD: An OrderedDict of {fit ESP method: mmjag keyword value} used to populate 'Fot ESP to' combo box. :vartype FIT_ESP_METHOD: `collections.OrderedDict` :cvar CONSTRAINTS_METHOD: An OrderedDict of {constraints method: mmjag keyword value} used to populate 'Constraints' combo box. :vartype CONSTRAINTS_METHOD: `collections.OrderedDict` """ FIT_ESP_METHOD = OrderedDict( (("Atom centers", mm.MMJAG_ICFIT_ATOM), ("Atom + bond midpoints", mm.MMJAG_ICFIT_BOND))) CONSTRAINTS_METHOD = OrderedDict( (("Total charge only", mm.MMJAG_INCDIP_CHG), ("Charge + dipole moment", mm.MMJAG_INCDIP_DIP), ("Charge -> quadrupole moment", mm.MMJAG_INCDIP_QUAD), ("Charge -> octupole moment", mm.MMJAG_INCDIP_OCT)))
[docs] def setup(self): # populate combo boxes self.ui.esp_fit_combo.addItemsFromDict(self.FIT_ESP_METHOD) self.ui.esp_const_combo.addItemsFromDict(self.CONSTRAINTS_METHOD) # set up validator self.ui.spacing_le.setValidator( QtGui.QDoubleValidator(0.0, sys.float_info.max, 5, self))
[docs] def getMmJagKeywords(self, checked): keywords = {} if checked: keywords[mm.MMJAG_IKEY_ICFIT] = self.ui.esp_fit_combo.currentData() keywords[mm.MMJAG_IKEY_INCDIP] = \ self.ui.esp_const_combo.currentData() gcharge = mm.MMJAG_GCHARGE_SPHFIT spacing = 0.75 if self.ui.esp_grid_rect_rb.isChecked(): gcharge = mm.MMJAG_GCHARGE_CUBFIT spacing = gui_utils.validate_le_float_input( self.ui.spacing_le, "Invalid input for spacing field.") keywords[mm.MMJAG_IKEY_GCHARGE] = gcharge keywords[mm.MMJAG_RKEY_WISPC] = spacing else: keywords[mm.MMJAG_IKEY_ICFIT] = mm.MMJAG_ICFIT_OFF keywords[mm.MMJAG_IKEY_INCDIP] = mm.MMJAG_INCDIP_CHG keywords[mm.MMJAG_IKEY_GCHARGE] = mm.MMJAG_GCHARGE_SPHFIT keywords[mm.MMJAG_RKEY_WISPC] = None return keywords
[docs] def loadSettings(self, keywords): item = self.ui.properties_tablew.item(ESP_ROW, 0) fit = keywords[mm.MMJAG_IKEY_ICFIT] if fit == mm.MMJAG_ICFIT_OFF: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked) self.ui.esp_fit_combo.setCurrentMmJagData(keywords, mm.MMJAG_IKEY_ICFIT, "Fit ESP method") self.ui.esp_const_combo.setCurrentMmJagData(keywords, mm.MMJAG_IKEY_INCDIP, "Constraints method") gcharge = keywords[mm.MMJAG_IKEY_GCHARGE] if gcharge == mm.MMJAG_GCHARGE_SPHFIT: self.ui.esp_grid_sphere_rb.setChecked(True) elif gcharge == mm.MMJAG_GCHARGE_CUBFIT: self.ui.esp_grid_rect_rb.setChecked(True) spacing = keywords[mm.MMJAG_RKEY_WISPC] self.ui.spacing_le.setText(str(spacing))
[docs]class MullikenSubTab(BaseSubTab): """ This sub tab class is used for Mulliken property. """
[docs] def getMmJagKeywords(self, checked): keywords = {} if checked: if self.ui.mulliken_by_atom_rb.isChecked(): mulken = mm.MMJAG_MULKEN_ATOM elif self.ui.mulliken_by_atom_and_basis_rb.isChecked(): mulken = mm.MMJAG_MULKEN_BASIS elif self.ui.mulliken_bond_populations_rb.isChecked(): mulken = mm.MMJAG_MULKEN_BOND keywords[mm.MMJAG_IKEY_MULKEN] = mulken else: keywords[mm.MMJAG_IKEY_MULKEN] = mm.MMJAG_MULKEN_OFF return keywords
[docs] def loadSettings(self, keywords): """ ATTENTION!!! The corresponding function in Maestro also had support for the following keywords: MMJAG_MULKEN_ATOM_GEOPT, MMJAG_MULKEN_BASIS_GEOPT and MMJAG_MULKEN_BOND_GEOPT. These were set using internal variable, which was not exposed in the GUI. This part of the code was not ported here to avoid possible errors. """ item = self.ui.properties_tablew.item(MULLIKEN_ROW, 0) mulken = keywords[mm.MMJAG_IKEY_MULKEN] if mulken == mm.MMJAG_MULKEN_OFF: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked) if mulken == mm.MMJAG_MULKEN_ATOM: self.ui.mulliken_by_atom_rb.setChecked(True) elif mulken == mm.MMJAG_MULKEN_BASIS: self.ui.mulliken_by_atom_and_basis_rb.setChecked(True) elif mulken == mm.MMJAG_MULKEN_BOND: self.ui.mulliken_bond_populations_rb.setChecked(True)
[docs]class PolarizabilitySubTab(BaseSubTab): """ This sub tab class is used for Polarizability property. :cvar PROPERTY_METHOD: An OrderedDict of {property method: mmjag keyword value} used to populate 'Property / Method' combo box. :vartype PROPERTY_METHOD: `collections.OrderedDict` """ PROPERTY_METHOD = OrderedDict(( ("alpha, beta / analytic", mm.MMJAG_IPOLAR_CPHF1), ("alpha, beta, gamma / analytic", mm.MMJAG_IPOLAR_CPHF2), ("alpha / 3-point finite field", mm.MMJAG_IPOLAR_3PT), ("alpha, beta / 3-point finite field", mm.MMJAG_IPOLAR_HYP3PT), ("alpha, beta / 5-point finite field", mm.MMJAG_IPOLAR_HYP5PT), ("alpha, beta / 7-point finite field", mm.MMJAG_IPOLAR_HYP7PT), ))
[docs] def setup(self): """ This function is used to define connections between widgets in this sub tab. """ # populate property / method combo box self.ui.polarizability_property_combo.addItemsFromDict( self.PROPERTY_METHOD) self.ui.polarizability_property_combo.currentIndexChanged.connect( self.methodChanged) self.ui.static_cb.stateChanged.connect(self.onStaticStateChanged) self.ui.dynamic_cb.stateChanged.connect(self.onDynamicStateChanged)
[docs] def methodChanged(self): """ This function is used to enable/disable 'finite field' widget when static polarizability property/method is changed. :param index: method combo box index :type index: int """ enabled = True ipolar = self.ui.polarizability_property_combo.currentData() if ipolar in (mm.MMJAG_IPOLAR_CPHF1, mm.MMJAG_IPOLAR_CPHF2): enabled = False self._setFieldWidgetsEnabled(enabled)
[docs] def getMmJagKeywords(self, checked): ipolar = mm.MMJAG_IPOLAR_OFF efield = None ifdpol = mm.MMJAG_IFDPOL_OFF fdpol_freq_1 = None fdpol_freq_2 = None if checked: ipolar = self.ui.polarizability_property_combo.currentData() efield = gui_utils.validate_le_float_input( self.ui.polarizability_field_le, "Invalid input for finite field.") if self.ui.dynamic_cb.checkState() == QtCore.Qt.Checked: ifdpol = mm.MMJAG_IFDPOL_HYPER fdpol_freq_1 = gui_utils.validate_le_float_input( self.ui.polarizability_freq_le, 'Invalid input for hyperpolarizability frequency.') fdpol_freq_2 = fdpol_freq_1 if self.ui.polarizability_opt_rect_rb.isChecked(): fdpol_freq_2 = -fdpol_freq_1 return { mm.MMJAG_IKEY_IPOLAR: ipolar, mm.MMJAG_RKEY_EFIELD: efield, mm.MMJAG_IKEY_IFDPOL: ifdpol, mm.MMJAG_RKEY_FDPOL_FREQ1: fdpol_freq_1, mm.MMJAG_RKEY_FDPOL_FREQ2: fdpol_freq_2 }
[docs] def loadSettings(self, keywords): item = self.ui.properties_tablew.item(POLARIZABILITY_ROW, 0) ipolar = keywords[mm.MMJAG_IKEY_IPOLAR] if ipolar == mm.MMJAG_IPOLAR_OFF: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked) self.ui.polarizability_property_combo.setCurrentMmJagData( keywords, mm.MMJAG_IKEY_IPOLAR, "polarizability property method") field = keywords[mm.MMJAG_RKEY_EFIELD] self.ui.polarizability_field_le.setText(str(field))
# TODO: Find frequency field, enable dynamic_cb, and set value if there is one
[docs] def onStaticStateChanged(self, state: Qt.CheckState): """ Update whether static options widgets are enabled. :param state: The current state of the static check box. """ if state == Qt.Unchecked: enabled = False self._setFieldWidgetsEnabled(enabled) elif state == Qt.Checked: enabled = True self.methodChanged() self.ui.polarizability_property_lbl.setEnabled(enabled) self.ui.polarizability_property_combo.setEnabled(enabled)
[docs] def onDynamicStateChanged(self, state: Qt.CheckState): """ Update whether dynamic options widgets are enabled. :param state: The current state of the dynamic check box. """ ui = self.ui widgets = [ ui.polarizability_freq_type_lbl, ui.polarizability_shg_rb, ui.polarizability_opt_rect_rb, ui.polarizability_freq_lbl, ui.polarizability_freq_le, ui.polarizability_freq_units_lbl ] enabled = state == Qt.Checked for widget in widgets: widget.setEnabled(enabled)
def _setFieldWidgetsEnabled(self, enabled: bool): self.ui.polarizability_field_lbl.setEnabled(enabled) self.ui.polarizability_field_le.setEnabled(enabled) self.ui.polarizability_field_units_lbl.setEnabled(enabled)
[docs]class MultipoleSubTab(BaseSubTab): """ This sub tab class is used for Multipole property. """
[docs] def getMmJagKeywords(self, checked): keywords = {} run_multipole = mm.MMJAG_LDIPS_OFF if checked: run_multipole = mm.MMJAG_LDIPS_HEXA if self.theory_level in [ gui_utils.THEORY_LMP2, gui_utils.THEORY_RIMP2 ]: run_multipole = mm.MMJAG_LDIPS_DIPOLE keywords[mm.MMJAG_IKEY_LDIPS] = run_multipole return keywords
[docs] def loadSettings(self, keywords): item = self.ui.properties_tablew.item(MULTIPOLE_ROW, 0) if keywords[mm.MMJAG_IKEY_LDIPS] == mm.MMJAG_LDIPS_OFF: item.setCheckState(QtCore.Qt.Unchecked) else: item.setCheckState(QtCore.Qt.Checked)
[docs] def theoryUpdated(self, theory_level, dft_functional, spin_treatment, basis): """ FIXME """
[docs]class MossbauerSubTab(BaseSubTab): """ Mössbauer properties tab options. :cvar DEFAULT_MOSS_ATNUM: Default value for Mössbauer atomic number. :vartype DEFAULT_MOSS_ATNUM: int :cvar DEFAULT_MOSS_NUC_QUADRUPOLE_MOMENT: Default value for Mössbauer nuclear quadrupole moment. :vartype DEFAULT_MOSS_NUC_QUADRUPOLE_MOMENT: float """ DEFAULT_MOSS_ATNUM = 26 DEFAULT_MOSS_NUC_QUADRUPOLE_MOMENT = 0.16
[docs] def getMmJagKeywords(self, checked): # @overrides: BaseSubTab moss_on = mm.MMJAG_MOSSBAUER_ON if checked else mm.MMJAG_MOSSBAUER_OFF atomic_number, quadrupole_moment = None, None if checked: atomic_number = self.ui.atomic_number_sb.value() quadrupole_moment = self.ui.nuclear_quadrupole_moment_sb.value() return { mm.MMJAG_IKEY_MOSSBAUER: moss_on, mm.MMJAG_IKEY_MOSS_ATNUM: atomic_number, mm.MMJAG_RKEY_MOSS_NUC_QUADRUPOLE: quadrupole_moment, }
[docs] def loadSettings(self, keywords): # @overrides: BaseSubTab. moss_on = keywords[mm.MMJAG_IKEY_MOSSBAUER] == mm.MMJAG_MOSSBAUER_ON check_state = QtCore.Qt.Checked if moss_on else QtCore.Qt.Unchecked item = self.ui.properties_tablew.item(MOSSBAUER_ROW, 0) item.setCheckState(check_state) self.ui.atomic_number_sb.setValue(self.DEFAULT_MOSS_ATNUM) self.ui.nuclear_quadrupole_moment_sb.setValue( self.DEFAULT_MOSS_NUC_QUADRUPOLE_MOMENT)
[docs]class PropertiesTabBase(BaseTab): NAME = "Properties" HELP_TOPIC = "JAGUAR_TOPIC_PROPERTIES_FOLDER" # list of rows that should be disabled if self.excited_state is True ES_EXCLUDE_ROWS = [ VIBRATIONAL_ROW, POLARIZABILITY_ROW, NMR_ROW, VCD_ROW, RAMAN_ROW ] LMP2_ROWS = [VIBRATIONAL_ROW, SURFACES_ROW, ESP_ROW, NBO_ROW, RAMAN_ROW]
[docs] def setup(self): """ Common setup for all properties tabs. """ # variable to store current theory self.theory = gui_utils.THEORY_DFT # variable to store current spin treatment self.spin_treatment = None # variable to store current state of excited state from theory tab self.excited_state = False # variable that stores excited state information self.excited_state_info = None # current Hamiltonian from theory tab self.hamiltonian = "" # list of enabled rows for current theory settings self.allowed_rows = [] # dictionary that maps property row to sub tab object self.property_sub_tab = {} self._createValidators() self._createSubTabMapping() sel_model = self.ui.properties_tablew.selectionModel() sel_model.selectionChanged.connect(self.propertyTableSelectionChanged) self.updatePropertiesTable()
def _createValidators(self): """ This function creates validators for various line edit widgets in the properties tab. """ self.ui.polarizability_field_le.setValidator( QtGui.QDoubleValidator(-sys.float_info.max, sys.float_info.max, 5, self)) def _createSubTabMapping(self): """ This function creates some map of rows to sub tab object. We can also add connections to specific tabs here as well. """ self.property_sub_tab[VIBRATIONAL_ROW] = VibrationalSubTab(self.ui) self.property_sub_tab[SURFACES_ROW] = SurfacesSubTab(self.ui) self.property_sub_tab[ESP_ROW] = ESPSubTab(self.ui) self.property_sub_tab[MULLIKEN_ROW] = MullikenSubTab(self.ui) self.property_sub_tab[NBO_ROW] = \ BaseSubTab(self.ui, row_id=NBO_ROW) self.property_sub_tab[MULTIPOLE_ROW] = MultipoleSubTab(self.ui) self.property_sub_tab[POLARIZABILITY_ROW] = \ PolarizabilitySubTab(self.ui, row_id=POLARIZABILITY_ROW) self.property_sub_tab[NMR_ROW] = \ BaseSubTab(self.ui, row_id=NMR_ROW) self.property_sub_tab[FUKUI_ROW] = \ BaseSubTab(self.ui, row_id=FUKUI_ROW) self.property_sub_tab[STOCKHOLDER_ROW] = \ BaseSubTab(self.ui, row_id=STOCKHOLDER_ROW) self.property_sub_tab[VCD_ROW] = \ BaseSubTab(self.ui, row_id=VCD_ROW) self.property_sub_tab[ECD_ROW] = \ BaseSubTab(self.ui, row_id=ECD_ROW) self.property_sub_tab[RAMAN_ROW] = RamanSubTab(self.ui)
[docs] def propertyTableSelectionChanged(self, selected, deselected): """ Triggered when selection within the properties table changes. :param selcted: Indices of the table that were selected type selected: QItemSelection :param deselected: Indices of the table that were deselected :type deslected: QItemSelection """ # Only single row selection is allowed. sel_idxs = self.ui.properties_tablew.selectedIndexes() if not sel_idxs: self.ui.subtabs_sw.setVisible(False) else: self.ui.subtabs_sw.setVisible(True) self.ui.subtabs_sw.setCurrentIndex(sel_idxs[0].row())
[docs] def getMmJagKeywords(self): """ This function returns dictionary of mmjag keywords for this tab. :return: mmjag keywords dictionary :rtype: dict """ keywords = {} for row in range(0, self.ui.properties_tablew.rowCount()): item = self.ui.properties_tablew.item(row, 0) checked = item.checkState() == QtCore.Qt.Checked if row not in self.allowed_rows: checked = False sub_tab = self.property_sub_tab[row] prop_keywords = sub_tab.getMmJagKeywords(checked) keywords.update(prop_keywords) return keywords
[docs] def loadSettings(self, keywords): """ This function restores this tab from the keywords dictionary :param keywords: mmjag keywrods dictionary :type keywords: dict """ for row, sub_tab in self.property_sub_tab.items(): sub_tab.loadSettings(keywords)
[docs] def theoryOrBasisUpdated(self, theory_level, dft_functional, spin_treatment, excited_state_info, basis, hamiltonian): """ Respond to the user changing the level of theory or the basis set. :param theory: The current level of theory. Must be one of "DFT", "HF", "LMP2", "RIMP2" or "Mixed" :type theory: str :param dft_functional: If c{theory} is "DFT", the currently selected functional level. If `theory` is not "DFT" or if the user has not specified a functional level, should be None. :type dft_functional: str or NoneType :param spin_treatment: True if the spin restriction option is set to 'Unrestricted' in the theory tab. If `theory` is "LMP2" or "RIMP2", should be None. :type spin_treatment: bool :param excited_state_info: Tuple that stores excited state info. If `theory` is "LMP2" or "RIMP2", tuple will contain None values. :type excited_state_info: theory_tab.ExcitedStateInfo :param basis: The currently selected basis set without the polarization or diffuse levels (i.e. "6-31G" rather than "6-31G**+") :type basis: str :param hamiltonian: The current Hamiltonian setting from the theory tab. :type hamiltonian: str """ self.theory = theory_level self.hamiltonian = hamiltonian self.spin_treatment = spin_treatment # Here we treat excited_state None as False. This is done since # later we may call functions like setCheckState(), which can only # take boolean as an argument. self.excited_state_info = excited_state_info self.excited_state = False if excited_state_info.excited_state: self.excited_state = True else: self.ui.nto_for_tddft_cb.setChecked(False) self.ui.nto_for_tddft_cb.setEnabled(self.excited_state) self.updatePropertiesTable() # Call sub tabs functions that depend on theory settings for row in [VIBRATIONAL_ROW, SURFACES_ROW, MULTIPOLE_ROW]: sub_tab = self.property_sub_tab[row] sub_tab.theoryUpdated(theory_level, dft_functional, spin_treatment, basis)
[docs] def updatePropertiesTable(self): """ This function enables and disables rows in the properties table depending on current theory and theory options. It needs to be defined for each subclass. """ self.allowed_rows = self.getAllowedRows() self.filterRowsByExcitedState() self.enableRows()
[docs] def getAllowedRows(self): """ This function returns list of property rows that need to be enabled. This function needs to be reimplemented in derived classes. :return: list property rows that should be enabled :rtype: list """ raise NotImplementedError
[docs] def selectFirstAllowedPropertyRow(self): """ Find the first allowed row in the Properties table and select it if one exists. """ if not self.allowed_rows: return first_allowed = min(self.allowed_rows) self.ui.properties_tablew.selectRow(first_allowed)
[docs] def enableRows(self): """ This function updates properties table to enable rows that were specified in self.allowed_rows member variable. """ sel_rows = { idx.row() for idx in self.ui.properties_tablew.selectedIndexes() } if not sel_rows or not sel_rows.issubset(self.allowed_rows): self.ui.properties_tablew.clearSelection() self.selectFirstAllowedPropertyRow() for row in range(0, self.ui.properties_tablew.rowCount()): for col in range(0, 2): item = self.ui.properties_tablew.item(row, col) #item.setFlags(item.flags() & QtCore.Qt.ItemIsSelectable) if row in self.allowed_rows: item.setFlags(item.flags() | QtCore.Qt.ItemIsEnabled) else: item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEnabled)
[docs] def filterRowsByExcitedState(self): """ If self.excited_states variable is True remove certain properties from list of enabled rows stored in self.allowed_rows. """ exclude_ecd = True info = self.excited_state_info if info: itda = info.excited_state_mode == mm.MMJAG_ITDA_ON # Only 'Singlet' is allowed. 'Singlet & Triplet' is excluded. singlet = (info.singlet == mm.MMJAG_RSINGLET_ON and info.triplet == mm.MMJAG_RTRIPLET_OFF) restricted = self.spin_treatment == SpinTreatment.Restricted if all([self.excited_state, itda, singlet, restricted]): exclude_ecd = False # Determine whether ECD row should be excluded exclude_rows = self.ES_EXCLUDE_ROWS.copy() if exclude_ecd: exclude_rows.append(ECD_ROW) if self.excited_state: rows = [x for x in self.allowed_rows if x not in exclude_rows] self.allowed_rows = rows elif exclude_ecd and ECD_ROW in self.allowed_rows: self.allowed_rows.remove(ECD_ROW)
[docs] def getSpecialProperties(self): """ This function returns a list of checked properties that can not be calculated for entries with spin multiplicity > 1. This function needs to be implemented in derived classes. """ raise NotImplementedError
def _allowedRowChecked(self, row): """ Check whether the given row is allowed and checked :type row: int :param row: The index of the row to check :rtype: bool :return: True if the row is checked and allowed """ item = self.ui.properties_tablew.item(row, 0) checked = item.checkState() == QtCore.Qt.Checked return checked and row in self.allowed_rows def _getCheckedProperties(self, input_rows): """ This function checks whether any of properties in a supplied list are checked and returns a list of names of these properties. :param input_rows: list of property row numbers :type input_rows: list :return: list of property names :rtype: list """ names = [] for row in input_rows: if self._allowedRowChecked(row): cur_name = str(self.ui.properties_tablew.item(row, 1).text()) names.append(cur_name) return names
[docs] def getCheckedRowNumbers(self): """ Return a list of all checked row numbers :rtype: list :return: Each item is the index of a checked row """ return [x for x in range(NUM_ROWS) if self._allowedRowChecked(x)]
[docs] def getSettingsAffectedBySpinTreatment(self): """ Get all mmjag values that need to be set on a per-structure basis dependent on the spin treatment and the spin multiplicity. :return: A tuple of: - A dictionary of {keyword: value} for all settings required for an unrestricted spin treatment - A dictionary of {keyword: value} for all settings required for a restricted spin treatment :rtype: tuple """ sub_tab = self.property_sub_tab[SURFACES_ROW] return sub_tab.getSettingsAffectedBySpinTreatment()
[docs]class PropertiesTab(PropertiesTabBase): UI_MODULES = (ui.properties_tab_ui,)
[docs] def getAllowedRows(self): """ This function returns list of property rows that need to be enabled for all tasks, but IGO. :return: list property rown that should be enabled :rtype: list """ if self.hamiltonian == mm.MMJAG_RELHAM_ZORA_2C: allowed_rows = [MULTIPOLE_ROW] elif self.theory in (gui_utils.THEORY_DFT, gui_utils.THEORY_HF): allowed_rows = [ VIBRATIONAL_ROW, SURFACES_ROW, ESP_ROW, MULLIKEN_ROW, NBO_ROW, MULTIPOLE_ROW, POLARIZABILITY_ROW, NMR_ROW, FUKUI_ROW, STOCKHOLDER_ROW, VCD_ROW, ECD_ROW, RAMAN_ROW ] elif self.theory in [gui_utils.THEORY_LMP2, gui_utils.THEORY_RIMP2]: allowed_rows = self.LMP2_ROWS.copy() allowed_rows.append(MOSSBAUER_ROW) return allowed_rows
[docs] def getSpecialProperties(self): if self.theory in (gui_utils.THEORY_DFT, gui_utils.THEORY_HF): disallowed_rows = [NMR_ROW] if self.spin_treatment == SpinTreatment.Restricted: disallowed_rows = [POLARIZABILITY_ROW, NMR_ROW] elif self.theory in [gui_utils.THEORY_LMP2, gui_utils.THEORY_RIMP2]: disallowed_rows = [VIBRATIONAL_ROW, ESP_ROW] names = self._getCheckedProperties(disallowed_rows) return names
def _createSubTabMapping(self): # @override: PropertiesTabBase. super()._createSubTabMapping() self.property_sub_tab[MOSSBAUER_ROW] = MossbauerSubTab( self.ui, row_id=MOSSBAUER_ROW)
[docs]class PropertiesTabIGO(PropertiesTabBase): UI_MODULES = (ui.properties_tab_ui,)
[docs] def getAllowedRows(self): """ This function returns list of property rows that need to be enabled for IGO task. :return: list property rown that should be enabled :rtype: list """ if self.hamiltonian == mm.MMJAG_RELHAM_ZORA_2C: allowed_rows = [MULTIPOLE_ROW] elif self.theory in (gui_utils.THEORY_DFT, gui_utils.THEORY_HF): allowed_rows = [ SURFACES_ROW, ESP_ROW, MULLIKEN_ROW, MULTIPOLE_ROW, STOCKHOLDER_ROW ] elif self.theory in [gui_utils.THEORY_LMP2, gui_utils.THEORY_RIMP2]: allowed_rows = self.LMP2_ROWS.copy() return allowed_rows
[docs] def getSpecialProperties(self): if self.theory in (gui_utils.THEORY_DFT, gui_utils.THEORY_HF): disallowed_rows = [] elif self.theory in [gui_utils.THEORY_LMP2, gui_utils.THEORY_RIMP2]: disallowed_rows = [VIBRATIONAL_ROW, ESP_ROW] names = self._getCheckedProperties(disallowed_rows) return names