Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CI calculations and better handling of transition states #136

Merged
merged 4 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
=======
History
=======
2024.8.17 --
* Added ability to do the various types of CI calculations that MOPAC supports.
* Improved the handling of TS calculations and added NLLSQ and SIGMA methods in
the optimization step.
* Added option to correctly handle transition states in the thermodynamics step and
improved the output to include the imaginary and low-lying frequencies.

2024.7.29 -- Bugfix in bond analysis for atoms and mopac.ini
* Fixed a bug in the bond analysis that caused the code to crash for calculations on
atoms.
Expand Down
310 changes: 202 additions & 108 deletions mopac_step/energy.py

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions mopac_step/energy_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ class EnergyParameters(seamm.Parameters):
"description": "Hamiltonian:",
"help_text": ("The Hamiltonian (parameterization) to use."),
},
"calculation": {
"default": "HF: Hartree-Fock",
"kind": "enumeration",
"default_units": "",
"enumeration": (
"HF: Hartree-Fock",
"CASCI: Complete active space CI",
"CIS: CI with singles",
"CISD: CI with singles and doubles",
"CISDT: CI with singles, doubles, and triples",
),
"format_string": "s",
"description": "Calculation:",
"help_text": ("The type of calculation."),
},
"convergence": {
"default": "normal",
"kind": "enumeration",
Expand Down Expand Up @@ -104,6 +119,45 @@ class EnergyParameters(seamm.Parameters):
"description": "UHF for singlets:",
"help_text": "Whether to use UHF for singlet states.",
},
"number ci orbitals": {
"default": 2,
"kind": "integer",
"default_units": "",
"enumeration": tuple(),
"format_string": "",
"description": "Number of orbitals in CI:",
"help_text": "The number of orbitals to use in the CI.",
},
"number doubly occupied ci orbitals": {
"default": "default",
"kind": "integer",
"default_units": "",
"enumeration": ("default",),
"format_string": "",
"description": "Number of doubly occupied orbitals in CI:",
"help_text": "The number of doubly occupied orbitals to use in the CI.",
},
"ci root": {
"default": 1,
"kind": "integer",
"default_units": "",
"enumeration": tuple(),
"format_string": "",
"description": "CI root:",
"help_text": "The root to use in the CI.",
},
"print ci details": {
"default": "yes",
"kind": "boolean",
"default_units": "",
"enumeration": (
"yes",
"no",
),
"format_string": "s",
"description": "Print the details of CI:",
"help_text": "Print the details of the CI.",
},
"COSMO": {
"default": "no",
"kind": "boolean",
Expand Down
8 changes: 7 additions & 1 deletion mopac_step/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ def description_text(self, P=None):
text += "BFGS method."
elif P["method"][0:5] == "L-BFGS":
text += "L-BFGS small memory version of the BFGS method."
elif P["method"].startswith("TS"):
text += "EF method for a transition state."
elif P["method"].startswith("SIGMA"):
text += "McIver-Komornicki method for transition states."
elif P["method"].startswith("NLLSQ"):
text += "Bartel's method of nonlinear least squares of the gradient."
else:
text += "optimization method determined at runtime by '{method}'."

Expand All @@ -79,7 +85,7 @@ def description_text(self, P=None):
text += " The geometrical convergence is {gnorm}."

# Put in the description of the energy calculation
text += "\n\nThe energy and forces will be c" + energy_description[1:]
text += "\n\n" + energy_description
text += "\n\n"

if self.is_expr(P["hamiltonian"]):
Expand Down
91 changes: 83 additions & 8 deletions mopac_step/thermodynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,23 @@ def description_text(self, P=None):
"\nThe thermodynamics functions will be calculated from "
"{Tmin} to {Tmax} in steps of {Tstep}. {trans} lowest "
"modes will be ignored to approximately account for {trans} "
"internal rotations.\n\n"
"internal rotations."
)
ts = P["transition state"]
if isinstance(ts, bool) and ts:
text += (
" Since the structure is a transition state, the lowest mode -- "
"the imaginary frequency -- will be ignored."
)
if isinstance(ts, str) and ts == "yes":
text += (
" Since the structure is a transition state, the lowest mode -- "
"the imaginary frequency -- will be ignored."
)
text += "\n\n"

# Put in the description of the energy calculation
text += "\n\nThe energy and forces will be c" + energy_description[1:]
text += "\n\n"
text += energy_description + "\n\n"

# Structure handling
text += "The structure in the standard orientation will {structure handling} "
Expand Down Expand Up @@ -130,7 +141,11 @@ def get_input(self):
if "1SCF" in keywords:
keywords.remove("1SCF")
keywords.append("THERMO=({Tmin},{Tmax},{Tstep})".format(**P))
keywords.append("TRANS={trans}".format(**P))
trans = P["trans"]
if P["transition state"]:
trans += 1
if trans > 0:
keywords.append(f"TRANS={trans}")

return inputs

Expand Down Expand Up @@ -211,6 +226,69 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None):
message = "Error creating the cif file\n\n" + traceback.format_exc()
logger.warning(message)

# First, how many rotations are there?
n_rot = sum([0 if PMI < 0.001 else 1 for PMI in data["PRI_MOM_OF_I"]])
n_vib = len(data["VIB._FREQ"]) - 3 - n_rot

# Check for imaginary frequencies
imaginary = []
low = []
for frequency in data["VIB._FREQ"][0:n_vib]:
if frequency < 0:
imaginary.append(frequency)
elif frequency < 100:
low.append(frequency)

n_trans = P["trans"]

text_lines = ""

if len(imaginary) > 0:
tmp = [f"{-f:.1f}i" for f in imaginary]
tmp = ", ".join(tmp)
if len(imaginary) == 1:
text_lines += (
textwrap.fill(
"The structure is a transition state with one-mode with an "
f"imaginary frequency of {tmp} cm^-1."
)
+ "\n\n"
)
else:
text_lines += (
textwrap.fill(
"The structure is a more general saddle point with "
f"{len(imaginary)} modes with imaginary frequencies: "
f"{tmp} cm^-1."
)
+ "\n\n"
)

if n_trans > 0:
tmp = [f"{-f:.1f}" for f in low[0:n_trans]]
tmp = ", ".join(tmp)
text_lines += (
textwrap.fill(
f"You asked that {n_trans} low-lying modes be ignored: {tmp} cm^-1."
" These should correspond to (almost) free rotors."
)
+ "\n\n"
)
low = low[n_trans:]

if len(low) > 0:
tmp = [f"{f:.1f}" for f in low]
tmp = ", ".join(tmp)
text_lines += (
textwrap.fill(
f"The structure has {len(low)} low-frequency modes: {tmp} cm^-1. "
"You may wish to exclude these from the thermodynamics if they are "
"(almost) free rotors that should not be handled within the "
"harmonic approximation."
)
+ "\n\n"
)

# Print the moments of inertia
p1, p2, p3 = data["PRI_MOM_OF_I"]
r1, r2, r3 = data["ROTAT_CONSTS"]
Expand All @@ -221,7 +299,7 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None):
"3": (p3, r3),
"Units": ("1.0E-40 g.cm^2", "1/cm"),
}
text_lines = " Rotational Constants\n"
text_lines += " Rotational Constants\n"
text_lines += tabulate(
tbl,
headers="keys",
Expand Down Expand Up @@ -261,9 +339,6 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None):
writer.writerow(row)

# And the vibrational modes to a csv file
# First, how many rotations are there?
n_rot = sum([0 if PMI < 0.001 else 1 for PMI in data["PRI_MOM_OF_I"]])
n_vib = len(data["VIB._FREQ"]) - 3 - n_rot
with open(directory / "vibrations.csv", "w", newline="") as fd:
writer = csv.writer(fd)
writer.writerow(
Expand Down
9 changes: 9 additions & 0 deletions mopac_step/thermodynamics_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ class ThermodynamicsParameters(mopac_step.EnergyParameters):
"will be ignored, which is a first approximation."
),
},
"transition state": {
"default": "no",
"kind": "boolean",
"default_units": "",
"enumeration": ("yes", "no"),
"format_string": "",
"description": "Transition state?:",
"help_text": "Whether this is a transition state.",
},
}

def __init__(self, defaults={}, data=None):
Expand Down
52 changes: 38 additions & 14 deletions mopac_step/tk_energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def create_dialog(self, title="Edit MOPAC Energy Step"):
self[key] = P[key].widget(e_frame)

# Set the callbacks for changes
for widget in ("convergence", "MOZYME", "COSMO"):
for widget in ("calculation", "convergence", "MOZYME", "COSMO"):
w = self[widget]
w.combobox.bind("<<ComboboxSelected>>", self.reset_energy_frame)
w.combobox.bind("<Return>", self.reset_energy_frame)
Expand Down Expand Up @@ -112,13 +112,25 @@ def reset_energy_frame(self, widget=None):
for slave in frame.grid_slaves():
slave.grid_forget()

calculation = self["calculation"].get()
convergence = self["convergence"].get()
cosmo = self["COSMO"].get()
mozyme = self["MOZYME"].get()

widgets = []
row = 0
for key in ("hamiltonian", "uhf", "convergence"):
for key in ("hamiltonian", "calculation"):
self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self[key])
row += 1

if "hf" in calculation.lower():
for key in ("uhf",):
self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self[key])
row += 1

for key in ("convergence",):
self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self[key])
row += 1
Expand All @@ -137,19 +149,31 @@ def reset_energy_frame(self, widget=None):
row += 1
sw.align_labels((self["relative"], self["absolute"]), sticky=tk.E)

self["MOZYME"].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self["MOZYME"])
row += 1
subwidgets = []
if mozyme != "always" and mozyme != "never":
self["nMOZYME"].grid(row=row, column=1, sticky=tk.W)
subwidgets.append(self["nMOZYME"])
row += 1
if mozyme != "never":
self["MOZYME follow-up"].grid(row=row, column=1, sticky=tk.W)
subwidgets.append(self["MOZYME"])
if "hf" in calculation.lower():
self["MOZYME"].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self["MOZYME"])
row += 1
sw.align_labels(subwidgets, sticky=tk.E)
subwidgets = []
if mozyme != "always" and mozyme != "never":
self["nMOZYME"].grid(row=row, column=1, sticky=tk.W)
subwidgets.append(self["nMOZYME"])
row += 1
if mozyme != "never":
self["MOZYME follow-up"].grid(row=row, column=1, sticky=tk.W)
subwidgets.append(self["MOZYME"])
row += 1
sw.align_labels(subwidgets, sticky=tk.E)

if "ci" in calculation.lower():
for key in (
"number ci orbitals",
"number doubly occupied ci orbitals",
"ci root",
"print ci details",
):
self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self[key])
row += 1

self["COSMO"].grid(row=row, column=0, columnspan=2, sticky=tk.EW)
widgets.append(self["COSMO"])
Expand Down
2 changes: 1 addition & 1 deletion mopac_step/tk_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def reset_optimization_frame(self, widget=None):
self["gnorm"].grid(row=row, column=1, sticky=tk.EW)
widgets_2.append(self["gnorm"])
row += 1
if method[0:2] == "EF" or method[0] == "$":
if method.startswith("EF") or method.startswith("TS") or self.is_expr(method):
self["recalc"].grid(row=row, column=1, sticky=tk.EW)
widgets_2.append(self["recalc"])
row += 1
Expand Down
Loading