Skip to content

Commit d743a69

Browse files
authored
CP2K tweaks (#1056)
* refactor dict merges to use pipe operator plus typo fix * more readable var names * Cp2kInputSet.is_valid warn that not implemented * Cp2kInputGenerator.get_input_set allow optional_files=False, meaning no optional files will be written before there was no way to disable writing potentially redundant BASIS and POTENTIAL files * don't write kpoints if user_kpoints_settings or base KPOINTS config is None KPOINTS are not compatible with orbital transformation (OT) mode and CP2K will crash if found * add asserts in cp2k/test_drones.py test_structure_optimization * fix test_structure_optimization assert doc.completed_at * more unit tests in cp2k/test_files.py * more atomate2.cp2k.powerups unit tests
1 parent 0fb73a9 commit d743a69

File tree

26 files changed

+420
-160
lines changed

26 files changed

+420
-160
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ default_language_version:
33
exclude: ^(.github/|tests/test_data/abinit/)
44
repos:
55
- repo: https://github.com/charliermarsh/ruff-pre-commit
6-
rev: v0.6.9
6+
rev: v0.7.3
77
hooks:
88
- id: ruff
99
args: [--fix]
@@ -17,7 +17,7 @@ repos:
1717
- id: end-of-file-fixer
1818
- id: trailing-whitespace
1919
- repo: https://github.com/asottile/blacken-docs
20-
rev: 1.18.0
20+
rev: 1.19.1
2121
hooks:
2222
- id: blacken-docs
2323
additional_dependencies: [black]
@@ -30,7 +30,7 @@ repos:
3030
- id: rst-directive-colons
3131
- id: rst-inline-touching-normal
3232
- repo: https://github.com/pre-commit/mirrors-mypy
33-
rev: v1.11.2
33+
rev: v1.13.0
3434
hooks:
3535
- id: mypy
3636
files: ^src/
@@ -45,7 +45,7 @@ repos:
4545
args: [--ignore-words-list, 'titel,statics,ba,nd,te,atomate']
4646
types_or: [python, rst, markdown]
4747
- repo: https://github.com/kynan/nbstripout
48-
rev: 0.7.1
48+
rev: 0.8.0
4949
hooks:
5050
- id: nbstripout
5151
args:

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,28 @@ dependencies = [
3434
"numpy",
3535
"pydantic-settings>=2.0.3",
3636
"pydantic>=2.0.1",
37-
"pymatgen>=2024.10.3",
37+
"pymatgen>=2024.11.13",
3838
]
3939

4040
[project.optional-dependencies]
4141
abinit = ["abipy>=0.9.3"]
4242
amset = ["amset>=0.4.15", "pydash"]
4343
cclib = ["cclib"]
4444
mp = ["mp-api>=0.37.5"]
45-
phonons = ["phonopy>=1.10.8", "seekpath"]
45+
phonons = ["phonopy>=1.10.8", "seekpath>=2.0.0"]
4646
lobster = ["ijson>=3.2.2", "lobsterpy>=0.4.0"]
4747
defects = [
4848
"dscribe>=1.2.0",
4949
"pymatgen-analysis-defects>=2024.5.11",
50-
"python-ulid",
50+
"python-ulid>=2.7",
5151
]
5252
forcefields = [
5353
"ase>=3.23.0",
5454
"calorine<=2.2.1",
5555
"chgnet>=0.2.2",
5656
"mace-torch>=0.3.3",
57-
"torchdata<=0.7.1",
5857
"matgl>=1.1.3",
58+
"torchdata<=0.7.1",
5959
# quippy-ase support for py3.12 tracked in https://github.com/libAtoms/QUIP/issues/645
6060
"quippy-ase>=0.9.14; python_version < '3.12'",
6161
"sevenn>=0.9.3",
@@ -112,7 +112,7 @@ strict = [
112112
"pydantic-settings==2.6.1",
113113
"pydantic==2.9.2",
114114
"pymatgen-analysis-defects==2024.7.19",
115-
"pymatgen==2024.10.3",
115+
"pymatgen==2024.11.13",
116116
"python-ulid==3.0.0",
117117
"seekpath==2.1.0",
118118
"tblite==0.3.0; python_version < '3.12'",

src/atomate2/common/jobs/mpmorph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def get_average_volume_from_mp_api(
7373
Returns
7474
-------
7575
float
76-
The average volume per atom for the composition.
76+
The average volume per atom for the composition in Angstrom^3.
7777
"""
7878
from mp_api.client import MPRester
7979

src/atomate2/common/schemas/magnetism.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class MagneticOrderingRelaxation(BaseModel):
8181
None, description="Total magnetization normalized to per formula unit."
8282
)
8383
total_magnetization_per_unit_volume: Optional[float] = Field(
84-
None, description="Total magnetiation normalized to per unit volume."
84+
None, description="Total magnetization normalized to per unit volume."
8585
)
8686

8787
@classmethod
@@ -175,7 +175,7 @@ class MagneticOrderingOutput(BaseModel):
175175
None, description="Total magnetization normalized to per formula unit."
176176
)
177177
total_magnetization_per_unit_volume: Optional[float] = Field(
178-
None, description="Total magnetiation normalized to per unit volume."
178+
None, description="Total magnetization normalized to per unit volume."
179179
)
180180
ordering_changed: Optional[bool] = Field(
181181
None,

src/atomate2/cp2k/files.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,24 @@ def copy_cp2k_outputs(
6565
additional_cp2k_files = additional_cp2k_files or []
6666

6767
# find required files
68-
o = Cp2kOutput(src_dir / get_zfile(directory_listing, "cp2k.out"), auto_load=False)
69-
o.parse_files()
68+
cp2k_output = Cp2kOutput(
69+
src_dir / get_zfile(directory_listing, "cp2k.out"), auto_load=False
70+
)
71+
cp2k_output.parse_files()
7072
if restart_to_input:
7173
additional_cp2k_files += ("restart",)
7274

7375
# copy files
7476
additional_cp2k_files += ("wfn",)
7577
files = ["cp2k.inp", "cp2k.out"]
76-
for f in set(additional_cp2k_files):
77-
if f in o.filenames and o.filenames.get(f):
78-
if isinstance(o.filenames[f], str):
79-
files.append(Path(o.filenames[f]).name)
78+
for file in set(additional_cp2k_files):
79+
if file in cp2k_output.filenames and cp2k_output.filenames.get(file):
80+
if isinstance(cp2k_output.filenames[file], str):
81+
files.append(Path(cp2k_output.filenames[file]).name)
8082
else:
81-
files.append(Path(o.filenames[f][-1]).name)
83+
files.append(Path(cp2k_output.filenames[file][-1]).name)
8284
else:
83-
files.append(Path(f).name)
85+
files.append(Path(file).name)
8486
all_files = [
8587
get_zfile(directory_listing, r + relax_ext, allow_missing=True) for r in files
8688
]

src/atomate2/cp2k/flows/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ class HybridFlowMaker(Maker):
222222
initialize_with_pbe
223223
Whether or not to attach a pre-hybrid flow that can be used to
224224
kickstart the hybrid flow. This is treated differently than just
225-
stiching flows together, because of the screening done in
225+
stitching flows together, because of the screening done in
226226
__post_init__
227227
pbe_maker
228228
Maker for the initialization

src/atomate2/cp2k/run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def run_cp2k(
112112
else:
113113
raise ValueError(f"Unsupported {job_type=}")
114114

115-
c = Custodian(
115+
custodian = Custodian(
116116
handlers,
117117
jobs,
118118
validators=validators,
@@ -122,7 +122,7 @@ def run_cp2k(
122122
)
123123

124124
logger.info("Running CP2K using custodian.")
125-
c.run()
125+
custodian.run()
126126

127127

128128
def should_stop_children(

src/atomate2/cp2k/schemas/calculation.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,12 +480,14 @@ def _get_basis_and_potential_files(dir_name: Path) -> dict[Cp2kObject, DataFile]
480480
the basis/potential contained in these files.
481481
"""
482482
data: dict[Cp2kObject, DataFile] = {}
483-
if Path.exists(dir_name / "BASIS"):
484-
data[Cp2kObject.BASIS] = BasisFile.from_file(str(dir_name / "BASIS")) # type: ignore[index]
485-
if Path.exists(dir_name / "POTENTIAL"):
486-
data[Cp2kObject.POTENTIAL] = PotentialFile.from_file( # type: ignore[index]
487-
str(dir_name / "POTENTIAL")
488-
)
483+
for filename, cls, cp2k_object in (
484+
(dir_name / "BASIS", BasisFile, Cp2kObject.BASIS),
485+
(dir_name / "POTENTIAL", PotentialFile, Cp2kObject.POTENTIAL),
486+
):
487+
if filename.exists():
488+
content = filename.read_text().strip()
489+
if content not in ("None", ""): # ignore empty files
490+
data[cp2k_object] = cls.from_str(content) # type: ignore[index]
489491
return data
490492

491493

src/atomate2/cp2k/schemas/task.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,14 @@ def from_cp2k_calc_doc(cls, calc_doc: Calculation) -> Self:
211211
OutputSummary
212212
The calculation output summary.
213213
"""
214-
if calc_doc.output.ionic_steps:
215-
forces = calc_doc.output.ionic_steps[-1].get("forces")
216-
stress = calc_doc.output.ionic_steps[-1].get("stress")
214+
if calc_doc.output.ionic_steps: # also handles for static calculations
215+
final_step = calc_doc.output.ionic_steps[-1]
216+
forces = final_step.get("forces")
217+
# 2024-11-14 @janosh this method used to read "stress" from final_step
218+
# (still read as fallback). CP2K docs don't mention "stress", only
219+
# "stress_tensor". Unclear if this was a breaking change in CP2K or
220+
# should always have been stress_tensor in this code.
221+
stress = final_step.get("stress_tensor", final_step.get("stress"))
217222
else:
218223
forces = None
219224
stress = None
@@ -338,7 +343,7 @@ def from_directory(
338343
task_files = _find_cp2k_files(dir_name, volumetric_files=volumetric_files)
339344

340345
if len(task_files) == 0:
341-
raise FileNotFoundError("No CP2K files found!")
346+
raise FileNotFoundError(f"No CP2K files found in {dir_name}")
342347

343348
calcs_reversed = []
344349
all_cp2k_objects = []

src/atomate2/cp2k/sets/base.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from __future__ import annotations
44

55
import os
6+
import warnings
67
from copy import deepcopy
78
from dataclasses import dataclass, field
89
from importlib.resources import files as get_mod_path
910
from pathlib import Path
10-
from typing import Any
11+
from typing import Any, Literal
1112

1213
import numpy as np
1314
from monty.io import zopen
@@ -39,7 +40,7 @@ class Cp2kInputSet(InputSet):
3940
def __init__(
4041
self,
4142
cp2k_input: Cp2kInput,
42-
optional_files: dict | None = None,
43+
optional_files: dict | None | Literal[False] = None,
4344
) -> None:
4445
"""Initialize the set.
4546
@@ -144,6 +145,10 @@ def from_directory(
144145
@property
145146
def is_valid(self) -> bool:
146147
"""Whether the input set is valid."""
148+
warnings.warn(
149+
"Cp2kInputSet.is_valid is not yet implemented and will always return True.",
150+
stacklevel=2,
151+
)
147152
return True
148153

149154

@@ -182,7 +187,7 @@ def get_input_set(
182187
self,
183188
structure: Structure | Molecule = None,
184189
prev_dir: str | Path = None,
185-
optional_files: dict | None = None,
190+
optional_files: dict | None | Literal[False] = None,
186191
) -> Cp2kInputSet:
187192
"""Get a CP2K input set.
188193
@@ -197,6 +202,8 @@ def get_input_set(
197202
A previous directory to generate the input set from.
198203
optional_files
199204
Additional files (e.g. vdw kernel file) to be included in the input set.
205+
If False, no optional files will be included. Defaults to writing a BASIS
206+
and POTENTIAL file.
200207
201208
Returns
202209
-------
@@ -224,15 +231,16 @@ def get_input_set(
224231
prev_input,
225232
input_updates,
226233
)
227-
optional_files = optional_files or {}
228-
optional_files["basis"] = {
229-
"filename": "BASIS",
230-
"object": self._get_basis_file(cp2k_input=cp2k_input),
231-
}
232-
optional_files["potential"] = {
233-
"filename": "POTENTIAL",
234-
"object": self._get_potential_file(cp2k_input=cp2k_input),
235-
}
234+
if optional_files is not False:
235+
optional_files = optional_files or {}
236+
optional_files["basis"] = {
237+
"filename": "BASIS",
238+
"object": self._get_basis_file(cp2k_input=cp2k_input),
239+
}
240+
optional_files["potential"] = {
241+
"filename": "POTENTIAL",
242+
"object": self._get_potential_file(cp2k_input=cp2k_input),
243+
}
236244

237245
return Cp2kInputSet(cp2k_input=cp2k_input, optional_files=optional_files)
238246

@@ -323,11 +331,7 @@ def _get_input(
323331
# Generate base input but override with user input settings
324332
input_settings = recursive_update(input_settings, input_updates)
325333
input_settings = recursive_update(input_settings, self.user_input_settings)
326-
overrides = (
327-
input_settings.pop("override_default_params")
328-
if "override_default_params" in input_settings
329-
else {}
330-
)
334+
overrides = input_settings.pop("override_default_params", {})
331335
cp2k_input = DftSet(structure=structure, kpoints=kpoints, **input_settings)
332336

333337
for setting in input_settings:
@@ -386,6 +390,15 @@ def _get_kpoints(
386390
"""Get the kpoints object."""
387391
kpoints_updates = kpoints_updates or {}
388392

393+
# don't write kpoints if user_kpoints_settings or base KPOINTS config is None
394+
# KPOINTS are not compatible with orbital transformation mode and CP2K will
395+
# crash if found
396+
if (
397+
self.user_kpoints_settings is None
398+
or self.config_dict.get("KPOINTS") is None
399+
):
400+
return None
401+
389402
# use user setting if set otherwise default to base config settings
390403
if self.user_kpoints_settings != {}:
391404
kpt_config = deepcopy(self.user_kpoints_settings)

0 commit comments

Comments
 (0)