Skip to content

Commit

Permalink
Merge pull request #35 from molssi-seamm/dev
Browse files Browse the repository at this point in the history
Enhancements and bug fixes for thermochemistry
  • Loading branch information
paulsaxe authored Oct 5, 2024
2 parents 248a997 + 3ba7643 commit b65c17d
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 134 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
=======
History
=======
2024.10.5 -- Enhancements and bug fixes for thermochemistry
* Improved GUI for thermochemistry so that it automatically recognizes whether it is
after e.g. an optimization and configures appropriately.
* Fixed and issue with transferring the multipole moments to the JSON file

2024.7.30 -- Enhanced results and fixed problem with psi4.ini
* Added to the results by using cclib to parse the output.
* If ~/SEAMM/psi4.ini did not exist the version that was automatically created was
Expand Down
95 changes: 55 additions & 40 deletions psi4_step/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,7 @@ def get_input(self, calculation_type="energy", restart=None):
lines.append("#" * 80)

# Figure out what we are doing!
if P["level"] == "recommended":
method_string = P["method"]
else:
method_string = P["advanced_method"]

# Allow the full name, or the short name, or just pray.
if method_string in psi4_step.methods:
method = psi4_step.methods[method_string]["method"]
else:
method = method_string.lower()
for key in psi4_step.methods:
if psi4_step.methods[key]["method"] == method:
break
else:
method = method_string
method, functional, method_string = self.get_method()

# lines.append('set scf_type df')
# lines.append('set guess sad')
Expand Down Expand Up @@ -251,32 +237,12 @@ def get_input(self, calculation_type="energy", restart=None):

lines.append("")
if method == "dft":
if P["level"] == "recommended":
functional_string = P["functional"]
else:
functional_string = P["advanced_functional"]

# Allow the full name, or the short name, or just pray.
if functional_string in psi4_step.dft_functionals:
functional = psi4_step.dft_functionals[functional_string]["name"]
else:
functional = functional_string.lower()
for key in psi4_step.dft_functionals:
if psi4_step.dft_functionals[key]["name"] == functional:
break
else:
functional = functional_string

if (
P["dispersion"] != "none"
and len(psi4_step.dft_functionals[functional_string]["dispersion"]) > 1
):
functional = functional + "-" + P["dispersion"]
if restart is None:
lines.append(
f"Eelec, wfn = {calculation_type}('{functional}', return_wfn=True)"
)
lines.append(f"G = gradient('{functional}', ref_wfn=wfn)")
if calculation_type == "energy":
lines.append(f"G = gradient('{functional}', ref_wfn=wfn)")
else:
if calculation_type == "gradient":
lines.append(
Expand All @@ -294,7 +260,8 @@ def get_input(self, calculation_type="energy", restart=None):
lines.append(
f"Eelec, wfn = {calculation_type}('{method}', return_wfn=True)"
)
lines.append(f"G = gradient('{method}', ref_wfn=wfn)")
if calculation_type == "energy":
lines.append(f"G = gradient('{method}', ref_wfn=wfn)")
else:
if calculation_type == "gradient":
lines.append(
Expand Down Expand Up @@ -347,9 +314,9 @@ def get_input(self, calculation_type="energy", restart=None):
variables["_method"] = "{method}"
variables["_method_string"] = "{method_string}"
tmp = fix_multipoles(variables)
with open("{filename}", "w") as fd:
json.dump(fix_multipoles(variables), fd, sort_keys=True, indent=3)
json.dump(tmp, fd, sort_keys=True, indent=3)
"""
)

Expand All @@ -358,6 +325,54 @@ def get_input(self, calculation_type="energy", restart=None):

return "\n".join(lines)

def get_method(self):
"""Get the method and functional to use"""
P = self.parameters.current_values_to_dict(
context=seamm.flowchart_variables._data
)

if P["level"] == "recommended":
method_string = P["method"]
else:
method_string = P["advanced_method"]

# Allow the full name, or the short name, or just pray.
if method_string in psi4_step.methods:
method = psi4_step.methods[method_string]["method"]
else:
method = method_string.lower()
for key in psi4_step.methods:
if psi4_step.methods[key]["method"] == method:
break
else:
method = method_string

if method == "dft":
if P["level"] == "recommended":
functional_string = P["functional"]
else:
functional_string = P["advanced_functional"]

# Allow the full name, or the short name, or just pray.
if functional_string in psi4_step.dft_functionals:
functional = psi4_step.dft_functionals[functional_string]["name"]
else:
functional = functional_string.lower()
for key in psi4_step.dft_functionals:
if psi4_step.dft_functionals[key]["name"] == functional:
break
else:
functional = functional_string

if (
P["dispersion"] != "none"
and len(psi4_step.dft_functionals[functional_string]["dispersion"]) > 1
):
functional = functional + "-" + P["dispersion"]
else:
functional = method
return method, functional, method_string

def analyze(self, indent="", data={}, out=[]):
"""Parse the output and generating the text output and store the
data in variables for other stages to access
Expand Down
57 changes: 4 additions & 53 deletions psi4_step/psi4.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,64 +38,15 @@ def fix_multipoles(data):
it = iter(data.items())
for key, value in it:
if 'PROP ' == key[0:5]:
result[key[6:]] = value[0]
elif 'SCF DIPOLE' == key:
result[key] = value[0]
elif 'CURRENT DIPOLE' == key:
result[key] = value[0]
elif '32-POLE' in key:
tmp = []
while True:
value = 0.0 if abs(value) < 1.0e-10 else value
tmp.append(value)
if 'ZZZZZ' in key:
break
key, value = next(it)
result['32-POLE'] = tmp
elif 'HEXADECAPOLE' in key:
tmp = []
while True:
value = 0.0 if abs(value) < 1.0e-10 else value
tmp.append(value)
if 'ZZZZ' in key:
break
key, value = next(it)
result['HEXADECAPOLE'] = tmp
elif 'OCTUPOLE' in key:
tmp = []
while True:
value = 0.0 if abs(value) < 1.0e-10 else value
tmp.append(value)
if 'ZZZ' in key:
break
key, value = next(it)
result['OCTUPOLE'] = tmp
elif 'QUADRUPOLE' in key:
tmp = []
while True:
value = 0.0 if abs(value) < 1.0e-10 else value
tmp.append(value)
if 'ZZ' in key:
break
key, value = next(it)
result['QUADRUPOLE'] = tmp
elif 'DIPOLE' in key:
tmp = []
while True:
value = 0.0 if abs(value) < 1.0e-10 else value
tmp.append(value)
result[key] = value
if 'Z' in key:
break
key, value = next(it)
result[key[0:-2]] = tmp
result[key[5:]] = value[0]
elif 'ESP AT CENTER' in key:
esp.append(value)
else:
result[key] = value
if len(esp) > 0:
result['ELECTROSTATIC POTENTIAL'] = esp
return result
"""

Expand Down Expand Up @@ -420,8 +371,8 @@ def run(self):
text = node.get_input()
input_data.append(text)

input_data.append("clean()")
input_data.append("clean_variables()")
# input_data.append("clean()")
# input_data.append("clean_variables()")
# input_data.append('clean_options()')

node = node.next()
Expand Down
52 changes: 21 additions & 31 deletions psi4_step/thermochemistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def git_revision(self):
"""The git version of this module."""
return psi4_step.__git_revision__

def description_text(self, P=None):
def description_text(self, P=None, configuration=None):
"""Create the text description of what this step will do.
The dictionary of control values is passed in as P so that
the code can test values, etc.
Expand All @@ -136,7 +136,9 @@ def description_text(self, P=None):
if not P:
P = self.parameters.values_to_dict()

text = super().description_text(P=P, calculation_type="Thermochemistry")
text = super().description_text(
P=P, calculation_type="Thermochemistry", configuration=configuration
)

added = (
"\nThe thermodynamic functions will be calculated at temperature {T} and "
Expand All @@ -146,7 +148,7 @@ def description_text(self, P=None):
return text + "\n" + __(added, **P, indent=4 * " ").__str__()

def get_input(self, calculation_type="frequency"):
"""Get the input for an optimization calculation for Psi4"""
"""Get the input for a frequency calculation for Psi4"""
_, configuration = self.get_system_configuration()

# Create the directory
Expand All @@ -165,29 +167,14 @@ def get_input(self, calculation_type="frequency"):
PP[key] = "{:~P}".format(PP[key])

self.description = []
self.description.append(__(self.description_text(PP), **PP, indent=self.indent))
self.description.append(
__(
self.description_text(PP, configuration=configuration),
**PP,
indent=self.indent,
)
)
# Figure out what we are doing! The method is HF, B3LYP, CCSD, etc.
if P["level"] == "recommended":
method_string = P["method"]
else:
method_string = P["advanced_method"]

if method_string in psi4_step.methods:
method = psi4_step.methods[method_string]["method"]
else:
method = method_string

if method == "dft":
if P["level"] == "recommended":
functional_string = P["functional"]
else:
functional_string = P["advanced_functional"]
method = psi4_step.dft_functionals[functional_string]["name"]
if (
P["dispersion"] != "none"
and len(psi4_step.dft_functionals[functional_string]["dispersion"]) > 1
):
method = method + "-" + P["dispersion"]

lines = []
lines.append("")
Expand All @@ -196,18 +183,21 @@ def get_input(self, calculation_type="frequency"):
lines.append("#" * 80)
lines.append("")
# lines.append("initial.find_point_group(tolerance=1.0e-5)")
lines.append("initial.symmetrize(1.0e-5)")
lines.append("point_group = initial.point_group().symbol()")
lines.append("")
# lines.append("initial.symmetrize(1.0e-5)")
# lines.append("point_group = initial.point_group().symbol()")
# lines.append("")

if not P["use existing parameters"]:
# Add in the input from the energy part of things
lines.append(super().get_input(calculation_type=calculation_type))
else:
lines.append(f"Eelec, wfn = frequency('{method}', return_wfn=True)")
previous = self.previous()
method, functional, _ = previous.get_method()

# Orbital plots
lines.append(self.plot_input())
if method == "dft":
lines.append(f"Eelec, wfn = frequency('{functional}', return_wfn=True)")
else:
lines.append(f"Eelec, wfn = frequency('{method}', return_wfn=True)")

lines.append(
f"""
Expand Down
35 changes: 25 additions & 10 deletions psi4_step/tk_thermochemistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def __init__(
None
"""
self.dialog = None
self.first_calculation = False

super().__init__(
tk_flowchart=tk_flowchart,
Expand Down Expand Up @@ -133,16 +134,28 @@ def create_dialog(self):

# and binding to change as needed
for key in ("use existing parameters",):
self[key].combobox.bind("<<ComboboxSelected>>", self.reset_thermochemistry)
self[key].combobox.bind("<Return>", self.reset_thermochemistry)
self[key].combobox.bind("<FocusOut>", self.reset_thermochemistry)
self[key].combobox.bind("<<ComboboxSelected>>", self.reset_dialog)
self[key].combobox.bind("<Return>", self.reset_dialog)
self[key].combobox.bind("<FocusOut>", self.reset_dialog)

# Top level needs to call reset_dialog
if self.node.calculation == "thermochemistry":
self.reset_dialog()

return frame

def edit(self):
"""Present a dialog for editing this step's parameters.
Look at the flowchart to see if a previous step was a calculation.
"""
previous = self.node.previous()
self.first_calculation = isinstance(previous, psi4_step.Initialization)

super().edit()

if self.first_calculation:
self["use existing parameters"].set("no")

def reset_dialog(self, widget=None, row=0):
"""Layout the widgets in the dialog.
Expand Down Expand Up @@ -172,18 +185,16 @@ def reset_dialog(self, widget=None, row=0):
for slave in frame.grid_slaves():
slave.grid_forget()

# Shortcut for parameters
P = self.node.parameters

self["thermochemistry"].grid(row=row, column=0)
row += 1

self.reset_thermochemistry()

if not P["use existing parameters"]:
if self.first_calculation or self["use existing parameters"].get() != "yes":
row = super().reset_dialog(row=row)
else:
self.reset_calculation()

self.fit_dialog()

return row

def reset_thermochemistry(self, widget=None):
Expand All @@ -194,7 +205,11 @@ def reset_thermochemistry(self, widget=None):
widgets = []
row = 0

for key in ("use existing parameters", "T", "P"):
if not self.first_calculation:
self["use existing parameters"].grid(row=row, column=0, sticky=tk.EW)
widgets.append(self["use existing parameters"])
row += 1
for key in ("T", "P"):
self[key].grid(row=row, column=0, sticky=tk.EW)
widgets.append(self[key])
row += 1
Expand Down

0 comments on commit b65c17d

Please sign in to comment.