Skip to content

Commit de3a81a

Browse files
committed
Merge branch 'main' of https://github.com/hackingmaterials/atomate into qcinput-vdw
2 parents 546d730 + a9cedff commit de3a81a

Some content is hidden

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

80 files changed

+2243555
-48
lines changed

CONTRIBUTING.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# Contributing to atomate
2+
23
We love your input! We want to make contributing to atomate as easy and transparent as possible, whether it's:
4+
35
* Reporting a bug
46
* Discussing the current state of the code
57
* Submitting a fix
68
* Proposing or implementing new features
79
* Becoming a maintainer
810

911
## Reporting bugs, getting help, and discussion
12+
1013
At any time, feel free to start a thread on our [Discourse forum](https://discuss.matsci.org/c/atomate).
1114

1215
If you are making a bug report, incorporate as many elements of the following as possible to ensure a timely response and avoid the need for followups:
16+
1317
* A quick summary and/or background
1418
* Steps to reproduce - be specific! **Provide sample code.**
1519
* What you expected would happen, compared to what actually happens
@@ -19,16 +23,19 @@ If you are making a bug report, incorporate as many elements of the following as
1923
We love thorough bug reports as this means the development team can make quick and meaningful fixes. When we confirm your bug report, we'll move it to the GitHub issues where its progress can be further tracked.
2024

2125
## Contributing code modifications or additions through Github
22-
We use github to host code, to track issues and feature requests, as well as accept pull requests. We maintain a list of all contributors to atomate [here.](https://atomate.org/contributors.html)
26+
27+
We use github to host code, to track issues and feature requests, as well as accept pull requests. We maintain a list of all contributors to atomate [here](https://atomate.org/contributors.html).
2328

2429
Pull requests are the best way to propose changes to the codebase. Follow the [Github flow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) for more information on this procedure.
2530

2631
The basic procedure for making a PR is:
32+
2733
* Fork the repo and create your branch from main.
2834
* Commit your improvements to your branch and push to your Github fork (repo).
2935
* When you're finished, go to your fork and make a Pull Request. It will automatically update if you need to make further changes.
3036

3137
### How to Make a **Great** Pull Request
38+
3239
We have a few tips for writing good PRs that are accepted into the main repo:
3340

3441
* Use the Google Code style for all of your code. Find an example [here.](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
@@ -41,4 +48,5 @@ When you submit your PR, our CI service will automatically run your tests.
4148
We welcome good discussion on the best ways to write your code, and the comments on your PR are an excellent area for discussion.
4249

4350
#### References
51+
4452
This document was adapted from the open-source contribution guidelines for Facebook's Draft, as well as briandk's [contribution template](https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62).

atomate/common/powerups.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,34 @@ def add_additional_fields_to_taskdocs(
130130
return original_wf
131131

132132

133+
def add_metadata(wf, meta_dict, fw_name_constraint=None):
134+
"""
135+
Add a metadata dictionary to a Workflow and all its Fireworks. The dictionary
136+
is merged into the "metadata" key of the Workflow and into the "_spec" key of
137+
each Firework in the workflow.
138+
139+
Can be used in combination with add_additional_fields_to_taskdocs to add the
140+
same set of key-value pairs to Workflows, Fireworks and Tasks collections.
141+
142+
Args:
143+
wf (Workflow)
144+
meta_dict: dictionary of custom metadata
145+
146+
Returns:
147+
Workflow
148+
"""
149+
150+
# add metadata to Workflow metadata
151+
wf.metadata.update(meta_dict)
152+
153+
# add metadata to Firework metadata
154+
for fw in wf.fws:
155+
if fw_name_constraint is None or fw_name_constraint in fw.name:
156+
fw.spec.update(meta_dict)
157+
158+
return wf
159+
160+
133161
def preserve_fworker(original_wf, fw_name_constraint=None):
134162
"""
135163
set _preserve_fworker spec of Fireworker(s) of a Workflow. Can be used to

atomate/common/tests/test_powerups.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22

3+
from atomate.utils.utils import get_fws_and_tasks
34
from atomate.vasp.workflows.base.core import get_wf
45

56
from pymatgen.io.vasp.sets import MPRelaxSet
@@ -10,6 +11,8 @@
1011
add_priority,
1112
add_tags,
1213
powerup_by_kwargs,
14+
add_additional_fields_to_taskdocs,
15+
add_metadata,
1316
)
1417
from fireworks import Firework, ScriptTask, Workflow
1518

@@ -137,6 +140,36 @@ def test_set_queue_adapter(self):
137140
)
138141
self.assertDictEqual(wf.id_fw[-3].spec, {})
139142

143+
def test_add_additional_fields_to_taskdocs(self):
144+
145+
my_wf = copy_wf(self.bsboltz_wf)
146+
meta_dict = {"foo": "bar", "baz": 42}
147+
my_wf = add_additional_fields_to_taskdocs(my_wf, meta_dict)
148+
149+
found = 0
150+
151+
for fw in my_wf.fws:
152+
for task in fw.tasks:
153+
if "ToDb" in str(task):
154+
for key, val in meta_dict.items():
155+
self.assertEqual(task["additional_fields"][key], val)
156+
157+
found += 1
158+
159+
self.assertEqual(found, 5)
160+
161+
def test_add_metadata(self):
162+
my_wf = copy_wf(self.bs_wf)
163+
my_wf.metadata = {"what": "ever"}
164+
meta_dict = {"foo": "bar", "baz": 42}
165+
my_wf = add_metadata(my_wf, meta_dict, fw_name_constraint="NonSCFFW")
166+
167+
self.assertEqual(my_wf.metadata, {"what": "ever", "foo": "bar", "baz": 42})
168+
169+
for [fw, _] in get_fws_and_tasks(my_wf, fw_name_constraint="NonSCFFW"):
170+
for key, val in meta_dict.items():
171+
self.assertEqual(fw.spec[key], val)
172+
140173

141174
def copy_wf(wf):
142175
return Workflow.from_dict(wf.to_dict())

atomate/vasp/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
# useful for storing duplicate of FW.json
3939
STORE_ADDITIONAL_JSON = False
4040

41+
# store Bader analysis when parsing VASP directories
42+
# (also requires "bader" executable to be in path)
43+
STORE_BADER = False
44+
4145
# vasp output files that will be copied to lobster run
4246
VASP_OUTPUT_FILES = [
4347
"OUTCAR",

atomate/vasp/drones.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
from atomate.utils.utils import get_logger
3737
from atomate import __version__ as atomate_version
38-
from atomate.vasp.config import STORE_VOLUMETRIC_DATA, STORE_ADDITIONAL_JSON
38+
from atomate.vasp.config import STORE_VOLUMETRIC_DATA, STORE_ADDITIONAL_JSON, STORE_BADER
3939

4040
__author__ = "Kiran Mathew, Shyue Ping Ong, Shyam Dwaraknath, Anubhav Jain"
4141
__email__ = "[email protected]"
@@ -44,7 +44,9 @@
4444

4545
logger = get_logger(__name__)
4646

47-
BADER_EXE_EXISTS = which("bader") or which("bader.exe")
47+
BADER_EXE_EXISTS = (which("bader") or which("bader.exe"))
48+
STORE_BADER = STORE_BADER and BADER_EXE_EXISTS
49+
4850

4951

5052
class VaspDrone(AbstractDrone):
@@ -135,7 +137,7 @@ def __init__(
135137
parse_locpot=True,
136138
additional_fields=None,
137139
use_full_uri=True,
138-
parse_bader=BADER_EXE_EXISTS,
140+
parse_bader=STORE_BADER,
139141
parse_chgcar=False,
140142
parse_aeccar=False,
141143
parse_potcar_file=True,
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
from fireworks import FiretaskBase, FWAction, explicit_serialize, Workflow
2+
from atomate.utils.utils import env_chk
3+
from atomate.vasp.database import VaspCalcDb
4+
from atomate.vasp.fireworks.approx_neb import ImageFW
5+
from atomate.common.powerups import powerup_by_kwargs
6+
7+
__author__ = "Ann Rutt"
8+
__email__ = "[email protected]"
9+
10+
11+
@explicit_serialize
12+
class GetImageFireworks(FiretaskBase):
13+
"""
14+
Adds ImageFWs to the workflow for the provided images_key
15+
according to the scheme specified by launch_mode. Optional
16+
parameters such as "handler_group", "add_additional_fields",
17+
and "add_tags" can be used to modify the resulting ImageFWs.
18+
19+
Args:
20+
db_file (str): path to file containing the database
21+
credentials.
22+
approx_neb_wf_uuid (str): unique id for approx neb workflow
23+
record keeping.
24+
images_key (str): specifies a key corresponding the images
25+
field of the approx_neb collection which specifies the
26+
desired combination of end points to interpolate images
27+
between. images_key should be a string of format "0+1",
28+
"0+2", etc. matching end_points_combo input of
29+
PathfinderToDb Firetask or pathfinder_key input of
30+
AddSelectiveDynamics Firetask. If images_key is not
31+
provided images will be launched for all paths/keys in
32+
the approx_neb collection images field.
33+
launch_mode (str): "all" or "screening"
34+
vasp_cmd (str): the name of the full executable for running
35+
VASP.
36+
Optional Params:
37+
vasp_input_set (VaspInputSet class): can use to
38+
define VASP input parameters.
39+
See pymatgen.io.vasp.sets module for more
40+
information. MPRelaxSet() and
41+
override_default_vasp_params are used if
42+
vasp_input_set = None.
43+
override_default_vasp_params (dict): if provided,
44+
vasp_input_set is disregarded and the Vasp Input
45+
Set is created by passing
46+
override_default_vasp_params to MPRelaxSet().
47+
Allows for easy modification of MPRelaxSet().
48+
For example, to set ISIF=2 in the INCAR use:
49+
{"user_incar_settings":{"ISIF":2}}
50+
handler_group (str or [ErrorHandler]): group of handlers to
51+
use for RunVaspCustodian firetask. See handler_groups
52+
dict in the code for the groups and complete list of
53+
handlers in each group. Alternatively, you can specify a
54+
list of ErrorHandler objects.
55+
add_additional_fields (dict): dict of additional fields to
56+
add to task docs (by additional_fields of VaspToDb).
57+
add_tags (list of strings): added to the "tags" field of the
58+
task docs.
59+
"""
60+
61+
required_params = [
62+
"db_file",
63+
"approx_neb_wf_uuid",
64+
"images_key",
65+
"launch_mode",
66+
"vasp_cmd",
67+
]
68+
optional_params = [
69+
"vasp_input_set",
70+
"override_default_vasp_params",
71+
"handler_group",
72+
"add_additional_fields",
73+
"add_tags",
74+
]
75+
76+
def run_task(self, fw_spec):
77+
# get the database connection
78+
db_file = env_chk(self["db_file"], fw_spec)
79+
mmdb = VaspCalcDb.from_db_file(db_file, admin=True)
80+
mmdb.collection = mmdb.db["approx_neb"]
81+
wf_uuid = self["approx_neb_wf_uuid"]
82+
launch_mode = self["launch_mode"]
83+
images_key = self["images_key"]
84+
85+
approx_neb_doc = mmdb.collection.find_one({"wf_uuid": wf_uuid}, {"images": 1})
86+
all_images = approx_neb_doc["images"]
87+
88+
# get structure_path of desired images and sort into structure_paths
89+
if images_key and isinstance(all_images, (dict)):
90+
images = all_images[images_key]
91+
max_n = len(images)
92+
if launch_mode == "all":
93+
structure_paths = [
94+
"images." + images_key + "." + str(n) + ".input_structure"
95+
for n in range(0, max_n)
96+
]
97+
elif launch_mode == "screening":
98+
structure_paths = self.get_and_sort_paths(
99+
max_n=max_n, images_key=images_key
100+
)
101+
elif isinstance(all_images, (dict)):
102+
structure_paths = dict()
103+
if launch_mode == "all":
104+
for key, images in all_images.items():
105+
max_n = len(images)
106+
structure_paths[key] = [
107+
"images." + key + "." + str(n) + ".input_structure"
108+
for n in range(0, max_n)
109+
]
110+
elif launch_mode == "screening":
111+
for key, images in all_images.items():
112+
structure_paths[key] = self.get_and_sort_paths(
113+
max_n=len(images), images_key=key
114+
)
115+
116+
# get list of fireworks to launch
117+
if isinstance(structure_paths, (list)):
118+
if isinstance(structure_paths[0], (str)):
119+
relax_image_fws = []
120+
for path in structure_paths:
121+
relax_image_fws.append(self.get_fw(structure_path=path))
122+
else:
123+
relax_image_fws = self.get_screening_fws(sorted_paths=structure_paths)
124+
elif isinstance(structure_paths, (dict)):
125+
relax_image_fws = []
126+
if launch_mode == "all":
127+
for key in structure_paths.keys():
128+
for path in structure_paths[key]:
129+
relax_image_fws.append(self.get_fw(structure_path=path))
130+
elif launch_mode == "screening":
131+
for key in structure_paths.keys():
132+
sorted_paths = structure_paths[key]
133+
relax_image_fws.extend(
134+
self.get_screening_fws(sorted_paths=sorted_paths)
135+
)
136+
137+
# place fws in temporary wf in order to use powerup_by_kwargs
138+
# to apply powerups to image fireworks
139+
if "vasp_powerups" in fw_spec.keys():
140+
temp_wf = Workflow(relax_image_fws)
141+
powerup_dicts = fw_spec["vasp_powerups"]
142+
temp_wf = powerup_by_kwargs(temp_wf, powerup_dicts)
143+
relax_image_fws = temp_wf.fws
144+
145+
return FWAction(additions=relax_image_fws)
146+
147+
def get_and_sort_paths(self, max_n, images_key=""):
148+
sorted_paths = [[], [], []]
149+
mid_n = int(max_n / 2)
150+
q1 = int((max_n - mid_n) / 2) # for second screening pass
151+
q3 = int((max_n + mid_n) / 2) # for second screening pass
152+
153+
for n in range(0, max_n):
154+
path = "images." + images_key + "." + str(n) + ".input_structure"
155+
if n == mid_n: # path for first screening pass (center image index)
156+
sorted_paths[0].append(path)
157+
elif n in [q1, q3]:
158+
sorted_paths[1].append(path)
159+
else:
160+
sorted_paths[-1].append(path)
161+
162+
return sorted_paths
163+
164+
def get_fw(self, structure_path, parents=None):
165+
add_tags = self.get("add_tags")
166+
fw = ImageFW(
167+
approx_neb_wf_uuid=self["approx_neb_wf_uuid"],
168+
structure_path=structure_path,
169+
db_file=self["db_file"],
170+
vasp_input_set=self.get("vasp_input_set"),
171+
vasp_cmd=self["vasp_cmd"],
172+
override_default_vasp_params=self.get("override_default_vasp_params"),
173+
handler_group=self.get("handler_group"),
174+
parents=parents,
175+
add_additional_fields=self.get("add_additional_fields"),
176+
add_tags=add_tags,
177+
)
178+
if isinstance(add_tags, (list)):
179+
if "tags" in fw.spec.keys():
180+
fw.spec["tags"].extend(add_tags)
181+
else:
182+
fw.spec["tags"] = add_tags
183+
return fw
184+
185+
def get_screening_fws(self, sorted_paths):
186+
if isinstance(sorted_paths, (list)) != True:
187+
if (
188+
any([isinstance(i, (list)) for i in sorted_paths]) != True
189+
or len(sorted_paths) != 3
190+
):
191+
raise TypeError("sorted_paths must be a list containing 3 lists")
192+
193+
s1_fw = self.get_fw(structure_path=sorted_paths[0][0])
194+
# ToDo: modify this firework to add firetask that checks whether to run/defuse children
195+
196+
s2_fws = []
197+
for path in sorted_paths[1]:
198+
s2_fws.append(self.get_fw(structure_path=path, parents=s1_fw))
199+
# ToDo: modify this firework to add firetask that checks whether to run/defuse children
200+
201+
remaining_fws = []
202+
for path in sorted_paths[-1]:
203+
remaining_fws.append(self.get_fw(structure_path=path, parents=s2_fws))
204+
205+
return [s1_fw] + s2_fws + remaining_fws

0 commit comments

Comments
 (0)