Skip to content

Commit e9b7fb0

Browse files
authored
Merge branch 'main' into random
2 parents 7507b74 + 8fe9f6f commit e9b7fb0

File tree

130 files changed

+268837
-392
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+268837
-392
lines changed

atomate/qchem/drones.py

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun):
215215
elif "ESP" in d_calc_final:
216216
d["output"]["esp"] = d_calc_final["ESP"][-1]
217217

218-
if (
219-
d["output"]["job_type"] == "opt"
220-
or d["output"]["job_type"] == "optimization"
221-
):
218+
if "nbo_data" in d_calc_final:
219+
d["output"]["nbo"] = d_calc_final["nbo_data"]
220+
221+
if d["output"]["job_type"] in ["opt", "optimization", "ts"]:
222222
if "molecule_from_optimized_geometry" in d_calc_final:
223223
d["output"]["optimized_molecule"] = d_calc_final[
224224
"molecule_from_optimized_geometry"
@@ -231,28 +231,44 @@ def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun):
231231
d_calc_final["opt_constraint"][0],
232232
float(d_calc_final["opt_constraint"][6]),
233233
]
234-
if (
235-
d["output"]["job_type"] == "freq"
236-
or d["output"]["job_type"] == "frequency"
237-
):
234+
if d["output"]["job_type"] in ["freq", "frequency"]:
238235
d["output"]["frequencies"] = d_calc_final["frequencies"]
236+
d["output"]["frequency_modes"] = d_calc_final["frequency_mode_vectors"]
239237
d["output"]["enthalpy"] = d_calc_final["total_enthalpy"]
240238
d["output"]["entropy"] = d_calc_final["total_entropy"]
241-
if (
242-
d["input"]["job_type"] == "opt"
243-
or d["input"]["job_type"] == "optimization"
244-
):
239+
if d["input"]["job_type"] in ["opt", "optimization", "ts"]:
245240
d["output"]["optimized_molecule"] = d_calc_final["initial_molecule"]
246241
d["output"]["final_energy"] = d["calcs_reversed"][1]["final_energy"]
242+
# For frequency-first calcs
243+
else:
244+
if len(d["calcs_reversed"]) > 1:
245+
first_calc = d["calcs_reversed"][-1]["input"]["rem"]["job_type"]
246+
second_calc = d["calcs_reversed"][-2]["input"]["rem"]["job_type"]
247+
if first_calc in ["freq", "frequency"] and second_calc in ["opt", "optimization", "ts"]:
248+
d["output"]["optimized_molecule"] = d_calc_final["initial_molecule"]
249+
d["output"]["final_energy"] = d["calcs_reversed"][1]["final_energy"]
250+
251+
if d["output"]["job_type"] == "pes_scan":
252+
d["output"]["scan_energies"] = d_calc_final.get("scan_energies")
253+
d["input"]["scan_variables"] = d_calc_final.get("scan_variables")
254+
d["output"]["scan_geometries"] = d_calc_final.get("optimized_geometries")
255+
d["output"]["scan_molecules"] = d_calc_final.get("molecules_from_optimized_geometries")
256+
257+
if d["output"]["job_type"] == "force":
258+
d["output"]["gradients"] = d_calc_final["gradients"][0]
259+
if d_calc_final["pcm_gradients"] is not None:
260+
d["output"]["pcm_gradients"] = d_calc_final["pcm_gradients"][0]
261+
if d_calc_final["CDS_gradients"] is not None:
262+
d["output"]["CDS_gradients"] = d_calc_final["CDS_gradients"][0]
247263

248264
opt_trajectory = []
249265
calcs = copy.deepcopy(d["calcs_reversed"])
250266
calcs.reverse()
251267
for calc in calcs:
252268
job_type = calc["input"]["rem"]["job_type"]
253-
if job_type == "opt" or job_type == "optimization":
254-
for ii, geom in enumerate(calc["geometries"]):
255-
site_properties = {"Mulliken": calc["Mulliken"][ii]}
269+
if job_type in ["opt", "optimization", "ts"]:
270+
for ii,geom in enumerate(calc["geometries"]):
271+
site_properties = {"Mulliken":calc["Mulliken"][ii]}
256272
if "RESP" in calc:
257273
site_properties["RESP"] = calc["RESP"][ii]
258274
mol = Molecule(
@@ -310,30 +326,36 @@ def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun):
310326

311327
d["state"] = "successful" if d_calc_final["completion"] else "unsuccessful"
312328
if "special_run_type" in d:
313-
if d["special_run_type"] == "frequency_flattener":
329+
if d["special_run_type"] in ["frequency_flattener", "ts_frequency_flattener"]:
314330
if d["state"] == "successful":
315-
orig_num_neg_freq = sum(
316-
1
317-
for freq in d["calcs_reversed"][-2]["frequencies"]
318-
if freq < 0
319-
)
320-
orig_energy = d_calc_init["final_energy"]
321-
final_num_neg_freq = sum(
322-
1 for freq in d_calc_final["frequencies"] if freq < 0
323-
)
331+
orig_num_neg_freq = None
332+
for calc in d["calcs_reversed"][::-1]:
333+
if "frequencies" in calc:
334+
orig_num_neg_freq = sum(1 for freq in calc["frequencies"] if freq < 0)
335+
break
336+
orig_energy = None
337+
for calc in d["calcs_reversed"][::-1]:
338+
if "final_energy" in calc:
339+
if calc["final_energy"] is not None:
340+
orig_energy = calc["final_energy"]
341+
break
342+
final_num_neg_freq = sum(1 for freq in d_calc_final["frequencies"] if freq < 0)
324343
final_energy = d["calcs_reversed"][1]["final_energy"]
325-
d["num_frequencies_flattened"] = (
326-
orig_num_neg_freq - final_num_neg_freq
327-
)
328-
if final_num_neg_freq > 0: # If a negative frequency remains,
329-
# and it's too large to ignore,
330-
if (
331-
final_num_neg_freq > 1
332-
or abs(d["output"]["frequencies"][0]) >= 15.0
333-
):
334-
d[
335-
"state"
336-
] = "unsuccessful" # then the flattening was unsuccessful
344+
if orig_num_neg_freq is None:
345+
d["num_frequencies_flattened"] = 0
346+
else:
347+
d["num_frequencies_flattened"] = orig_num_neg_freq - final_num_neg_freq
348+
349+
if d["special_run_type"] == "frequency_flattener":
350+
if final_num_neg_freq > 0: # If a negative frequency remains,
351+
# and it's too large to ignore,
352+
if final_num_neg_freq > 1 or abs(d["output"]["frequencies"][0]) >= 15.0:
353+
d["state"] = "unsuccessful" # then the flattening was unsuccessful
354+
else:
355+
if final_num_neg_freq > 1: # If a negative frequency remains,
356+
# and it's too large to ignore,
357+
if final_num_neg_freq > 2 or abs(d["output"]["frequencies"][1]) >= 15.0:
358+
d["state"] = "unsuccessful" # then the flattening was unsuccessful
337359
if final_energy > orig_energy:
338360
d["warnings"]["energy_increased"] = True
339361

atomate/qchem/firetasks/geo_transformations.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# This module defines firetasks for writing QChem input files
1+
# coding: utf-8
2+
3+
4+
# This module defines firetasks for modifying molecular geometries
5+
6+
import copy
27

38
import numpy as np
49
from fireworks import FiretaskBase, FWAction, explicit_serialize
@@ -58,3 +63,36 @@ def run_task(self, fw_spec):
5863
update_spec = {"prev_calc_molecule": rotated_mol}
5964

6065
return FWAction(update_spec=update_spec)
66+
67+
68+
@explicit_serialize
69+
class PerturbGeometry(FiretaskBase):
70+
"""
71+
Modify a Molecule geometry.
72+
"""
73+
74+
required_params = list()
75+
optional_params = ["molecule", "mode", "scale"]
76+
77+
def run_task(self, fw_spec):
78+
if self.get("molecule"):
79+
mol = self.get("molecule")
80+
elif fw_spec.get("prev_calc_molecule"):
81+
mol = fw_spec.get("prev_calc_molecule")
82+
else:
83+
raise KeyError("No molecule present; add as an optional param or check fw_spec")
84+
85+
if self.get("mode") is not None:
86+
mode = self.get("mode")
87+
else:
88+
raise KeyError("No mode present; add as an optional param or check fw_spec")
89+
90+
mol_copy = copy.deepcopy(mol)
91+
92+
for ii in range(len(mol)):
93+
vec = np.array(mode[ii])
94+
mol_copy.translate_sites(indices=[ii], vector=vec * self.get("scale", 1.0))
95+
96+
update_spec = {"prev_calc_molecule": mol_copy}
97+
98+
return FWAction(update_spec=update_spec)

atomate/qchem/firetasks/parse_outputs.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,14 @@ class QChemToDb(FiretaskBase):
4141
of this key in the fw_spec.
4242
multirun (bool): Whether the job to parse includes multiple
4343
calculations in one input / output pair.
44+
runs (list): Series of file suffixes that the Drone should look for
45+
when parsing output.
4446
"""
4547

4648
optional_params = [
47-
"calc_dir",
48-
"calc_loc",
49-
"input_file",
50-
"output_file",
51-
"additional_fields",
52-
"db_file",
53-
"fw_spec_field",
54-
"multirun",
49+
"calc_dir", "calc_loc", "input_file", "output_file",
50+
"additional_fields", "db_file", "fw_spec_field", "multirun",
51+
"runs"
5552
]
5653

5754
def run_task(self, fw_spec):
@@ -64,13 +61,14 @@ def run_task(self, fw_spec):
6461
input_file = self.get("input_file", "mol.qin")
6562
output_file = self.get("output_file", "mol.qout")
6663
multirun = self.get("multirun", False)
64+
runs = self.get("runs", None)
6765

6866
# parse the QChem directory
6967
logger.info(f"PARSING DIRECTORY: {calc_dir}")
7068

7169
additional_fields = self.get("additional_fields", {})
7270

73-
drone = QChemDrone(additional_fields=additional_fields)
71+
drone = QChemDrone(runs=runs, additional_fields=additional_fields)
7472

7573
# assimilate (i.e., parse)
7674
task_doc = drone.assimilate(

atomate/qchem/firetasks/run_calc.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from atomate.utils.utils import env_chk, get_logger
1616

17-
__author__ = "Samuel Blau"
17+
__author__ = "Samuel Blau, Evan Spotte-Smith"
1818
__copyright__ = "Copyright 2018, The Materials Project"
1919
__version__ = "0.1"
2020
__maintainer__ = "Samuel Blau"
@@ -91,22 +91,11 @@ class RunQChemCustodian(FiretaskBase):
9191

9292
required_params = ["qchem_cmd"]
9393
optional_params = [
94-
"multimode",
95-
"input_file",
96-
"output_file",
97-
"max_cores",
98-
"qclog_file",
99-
"suffix",
100-
"calc_loc",
101-
"save_scratch",
102-
"max_errors",
103-
"job_type",
104-
"handler_group",
105-
"gzipped_output",
106-
"backup",
107-
"linked",
108-
"max_iterations",
109-
"max_molecule_perturb_scale",
94+
"multimode", "input_file", "output_file", "max_cores", "qclog_file",
95+
"suffix", "calc_loc", "save_scratch", "max_errors", "job_type",
96+
"handler_group", "gzipped_output", "backup", "linked",
97+
"max_iterations", "max_molecule_perturb_scale", "freq_before_opt",
98+
"transition_state"
11099
]
111100

112101
def run_task(self, fw_spec):
@@ -135,6 +124,8 @@ def run_task(self, fw_spec):
135124
max_molecule_perturb_scale = self.get("max_molecule_perturb_scale", 0.3)
136125
job_type = self.get("job_type", "normal")
137126
gzipped_output = self.get("gzipped_output", True)
127+
transition_state = self.get("transition_state", False)
128+
freq_before_opt = self.get("freq_before_opt", False)
138129

139130
handler_groups = {
140131
"default": [
@@ -169,6 +160,8 @@ def run_task(self, fw_spec):
169160
qclog_file=qclog_file,
170161
max_iterations=max_iterations,
171162
linked=linked,
163+
freq_before_opt=freq_before_opt,
164+
transition_state=transition_state,
172165
save_final_scratch=save_scratch,
173166
max_cores=max_cores,
174167
calc_loc=calc_loc,
@@ -183,6 +176,8 @@ def run_task(self, fw_spec):
183176
max_iterations=max_iterations,
184177
max_molecule_perturb_scale=max_molecule_perturb_scale,
185178
linked=linked,
179+
freq_before_opt=freq_before_opt,
180+
transition_state=transition_state,
186181
save_final_scratch=save_scratch,
187182
max_cores=max_cores,
188183
calc_loc=calc_loc,

atomate/qchem/firetasks/tests/test_geo_transformations.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import os
22
import unittest
33

4-
import numpy as np
5-
from pymatgen.core import Molecule
6-
7-
from atomate.qchem.firetasks.geo_transformations import RotateTorsion
4+
from atomate.qchem.firetasks.geo_transformations import RotateTorsion, PerturbGeometry
85
from atomate.utils.testing import AtomateTest
6+
from pymatgen.core.structure import Molecule
7+
import numpy as np
8+
from pymatgen.io.qchem.outputs import QCOutput
9+
import numpy as np
910

10-
__author__ = "Brandon Wood"
11+
__author__ = "Brandon Wood, Evan Spotte-Smith"
1112
__email__ = "[email protected]"
1213

1314
module_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))
@@ -48,5 +49,39 @@ def test_rotate_torsion(self):
4849
)
4950

5051

52+
class TestPerturbGeometry(AtomateTest):
53+
@classmethod
54+
def setUpClass(cls):
55+
56+
cls.ts_init = Molecule.from_file(
57+
os.path.join(module_dir, "..", "..", "test_files",
58+
"ts_init.xyz"))
59+
cls.ts_perturbed = Molecule.from_file(
60+
os.path.join(module_dir, "..", "..", "test_files",
61+
"ts_perturbed.xyz"))
62+
cls.mode = QCOutput(os.path.join(
63+
module_dir, "..", "..", "test_files", "ts.out"
64+
)).data["frequency_mode_vectors"][0]
65+
66+
def setUp(self, lpad=False):
67+
super(TestPerturbGeometry, self).setUp(lpad=False)
68+
69+
def tearDown(self):
70+
pass
71+
72+
def test_perturb(self):
73+
ft = PerturbGeometry({
74+
"molecule": self.ts_init,
75+
"mode": self.mode,
76+
"scale": 1.0
77+
})
78+
pert_mol = ft.run_task({})
79+
test_mol = Molecule.from_dict(
80+
pert_mol.as_dict()["update_spec"]["prev_calc_molecule"])
81+
np.testing.assert_equal(self.ts_perturbed.species, test_mol.species)
82+
np.testing.assert_allclose(
83+
self.ts_perturbed.cart_coords, test_mol.cart_coords, atol=0.0001)
84+
85+
5186
if __name__ == "__main__":
5287
unittest.main()

0 commit comments

Comments
 (0)