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