Skip to content

Commit 49d1c1d

Browse files
4kevinbeck5tpurcell90JaGeo
authored
Anharmonicity Quantification workflow (#901)
* Generated displaced structures * Implemented sigma_A oneshot calculations * Added some needed object attributes * Added some object attributes * Added pieces to flow to utilize PhononBSDOSDoc * Added a prefactor method to find dynamical matrix * Fixed issue with prefactor method * Made creation of dynamical matrix indep of phonon * Added helper function to make Phonopy objects * Finished writing flow * Made all functions independent of passing Phonopy * Code for anharmonicity code and starter for test * o Debug sigma^A calculations 1) Temperature needs to be in eV (kB / e) 2) Consistent supercells: phonopy and pymatgen use different standards 3) Use a BasePhononMaker instead of copy/paste * Update anharmonicity tests * Use pymatgen.core.units.kb remove hard-coded constants * Fixed assert statement * The phonon supercell now determined to from phonon document If relaxation changes structure it must be read from phonon doc * Modification for mock_aims to work * Add the test data for mock aims Tests should now work outside of socket calcs * Add an aims only workflow and generalize the common one similar to what is done with the phonon workflow * Add functionality to create an anharmonicity flow from a PhononBSDOSDoc Default is still to start from phonons for consistency between anharmonic and phonon calcs * Actually add Aims workflow for anharm * Made to work with anharmonicity schema doc * Added function to make anharmonicity doc * Cleaned up anharmonicity flow test * Wrote tests for anharm schema * Added fields to anharm schema * Added flag and function for full sigma^A * Added files for mock_aims * Added test for full sigma A with mock_aims * Added capability to pass a seed for rng * Added files for mock aims * Added pytest fixture for NaCl structure * Added atom-resolved sigma A calculation and test * Fixed bug when atom_resolved was False * Added mock_aims files for mode_resolved sigma^A * Added mode resolved sigma^A test w/ mock_aims * Implemented mode resolved sigma^A calcs * Made tests work with sigma_dict * Returns a dictionary with computed sigma values * Cleaned up code with pre-commit * Fixed bug in test_anharmonicity.py * Fixed anharm quant one-shot socket test * Fixed typo in docstring * Added return info to docstrings * Refactored code to make cleaner * Fix lint errors in tests * Remove typing.Self call Tested on python 3.11 * Fixed lint and typing errors. * Fixed tests to pass on github. * Update src/atomate2/aims/flows/anharmonicity.py Co-authored-by: J. George <[email protected]> * Changed omega_to_THz to be imported from atomate2 * Made separate eigenvec/val solver for dynamical matrix and added clean_dir to tests * Fixed torchdata version * Re-implemented get_phonon_supercell * Added mode-resolved sigma^A calculation * Fixed linting issues * Add @4kevinbeck5 to contributors.md file * remove dict initalisation * test if pip on torch data is really needed --------- Co-authored-by: Tom Purcell <[email protected]> Co-authored-by: J. George <[email protected]> Co-authored-by: JaGeo <[email protected]>
1 parent 1ebc9a7 commit 49d1c1d

File tree

126 files changed

+1807
-70
lines changed

Some content is hidden

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

126 files changed

+1807
-70
lines changed

docs/about/contributors.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,11 @@ Hobbyist Contributor
129129

130130
[comprhys]: https://github.com/comprhys
131131
[0000-0002-6589-1700]: https://orcid.org/0000-0002-6589-1700
132+
133+
134+
**Kevin Beck** [![gh]][4kevinbeck5] [![orc]][0009-0006-5620-1292] \
135+
PhD student in Applied Mathematics \
136+
University of Arizona
137+
138+
[4kevinbeck5]: https://github.com/4kevinbeck5
139+
[0009-0006-5620-1292]: https://orcid.org/0009-0006-5620-1292
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Defines the anharmonicity quantification workflows for FHI-aims."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
from typing import TYPE_CHECKING
7+
8+
from atomate2.common.flows.anharmonicity import BaseAnharmonicityMaker
9+
10+
if TYPE_CHECKING:
11+
from atomate2.aims.flows.phonons import PhononMaker
12+
13+
14+
@dataclass
15+
class AnharmonicityMaker(BaseAnharmonicityMaker):
16+
"""
17+
Maker to calculate the anharmonicity score of a material.
18+
19+
Calculate sigma^A as defined in doi.org/10.1103/PhysRevMaterials.4.083809, by
20+
first calculating the phonons for a material and then generating the one-shot
21+
sample and calculating the DFT and harmonic forces.
22+
23+
Parameters
24+
----------
25+
name : str
26+
Name of the flows produced by this maker.
27+
phonon_maker: BasePhononMaker
28+
The maker to generate the phonon model
29+
"""
30+
31+
name: str = "anharmonicity"
32+
phonon_maker: PhononMaker = None
33+
34+
@property
35+
def prev_calc_dir_argname(self) -> str:
36+
"""Name of argument informing static maker of previous calculation directory.
37+
38+
As this differs between different DFT codes (e.g., VASP, CP2K), it
39+
has been left as a property to be implemented by the inheriting class.
40+
41+
Note: this is only applicable if a relax_maker is specified; i.e., two
42+
calculations are performed for each ordering (relax -> static)
43+
"""
44+
return "prev_dir"

src/atomate2/aims/run.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,29 @@ def run_aims_socket(
9292
aims_cmd: str
9393
The aims command to use (defaults to SETTINGS.AIMS_CMD).
9494
"""
95+
if aims_cmd is None:
96+
aims_cmd = SETTINGS.AIMS_CMD
97+
9598
ase_adaptor = AseAtomsAdaptor()
9699
atoms_to_calculate = [
97100
ase_adaptor.get_atoms(structure) for structure in structures_to_calculate
98101
]
99102

100103
with open("parameters.json") as param_file:
101104
parameters = json.load(param_file, cls=MontyDecoder)
105+
106+
del_keys = []
107+
for key, val in parameters.items():
108+
if val is None:
109+
del_keys.append(key)
110+
111+
for key in del_keys:
112+
parameters.pop(key)
113+
102114
if aims_cmd:
103-
parameters["aims_command"] = aims_cmd
104-
elif "aims_command" not in parameters:
105-
parameters["aims_command"] = SETTINGS.AIMS_CMD
115+
parameters["command"] = aims_cmd
116+
elif "command" not in parameters:
117+
parameters["command"] = SETTINGS.AIMS_CMD
106118

107119
calculator = Aims(**parameters)
108120
port = parameters["use_pimd_wrapper"][1]
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
"""A workflow to evaluate the anharmonicity of a material with sigma^A.
2+
3+
For details see: doi.org/10.1103/PhysRevMaterials.4.083809
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from abc import ABC, abstractmethod
9+
from dataclasses import dataclass
10+
from typing import TYPE_CHECKING
11+
from warnings import warn
12+
13+
from jobflow import Flow, Maker
14+
15+
from atomate2.common.jobs.anharmonicity import (
16+
displace_structure,
17+
get_forces,
18+
get_phonon_supercell,
19+
get_sigmas,
20+
run_displacements,
21+
store_results,
22+
)
23+
24+
if TYPE_CHECKING:
25+
from pathlib import Path
26+
27+
from emmet.core.math import Matrix3D
28+
from pymatgen.core.structure import Structure
29+
30+
from atomate2.common.flows.phonons import BasePhononMaker
31+
from atomate2.common.schemas.phonons import PhononBSDOSDoc
32+
33+
SUPPORTED_CODES = ["aims"]
34+
35+
36+
@dataclass
37+
class BaseAnharmonicityMaker(Maker, ABC):
38+
"""
39+
Maker to calculate the anharmonicity score of a material.
40+
41+
Calculate sigma^A as defined in doi.org/10.1103/PhysRevMaterials.4.083809, by
42+
first calculating the phonons for a material and then generating the one-shot
43+
sample and calculating the DFT and harmonic forces.
44+
45+
Parameters
46+
----------
47+
name: str
48+
Name of the flows produced by this maker.
49+
phonon_maker: BasePhononMaker
50+
The maker to generate the phonon model
51+
"""
52+
53+
name: str = "anharmonicity"
54+
phonon_maker: BasePhononMaker = None
55+
56+
def make(
57+
self,
58+
structure: Structure,
59+
prev_dir: str | Path | None = None,
60+
born: list[Matrix3D] | None = None,
61+
epsilon_static: Matrix3D | None = None,
62+
total_dft_energy_per_formula_unit: float | None = None,
63+
supercell_matrix: Matrix3D | None = None,
64+
temperature: float = 300,
65+
one_shot_approx: bool = True,
66+
seed: int | None = None,
67+
element_resolved: bool = False,
68+
mode_resolved: bool = False,
69+
site_resolved: bool = False,
70+
n_samples: int = 1,
71+
) -> Flow:
72+
"""Make the anharmonicity calculation flow.
73+
74+
Parameters
75+
----------
76+
structure: Structure
77+
A pymatgen structure object. Please start with a structure
78+
that is nearly fully optimized as the internal optimizers
79+
have very strict settings!
80+
prev_dir: Optional[str | Path]
81+
A previous calculation directory to use for copying outputs.
82+
Default is None.
83+
born: Optional[list[Matrix3D]]
84+
Instead of recomputing born charges and epsilon, these values can also be
85+
provided manually. If born and epsilon_static are provided, the born run
86+
will be skipped it can be provided in the VASP convention with information
87+
for every atom in unit cell. Please be careful when converting structures
88+
within in this workflow as this could lead to errors. The default for this
89+
field is None.
90+
epsilon_static: Optional[Matrix3D]
91+
The high-frequency dielectric constant to use instead of recomputing born
92+
charges and epsilon. If born, epsilon_static are provided, the born run
93+
will be skipped. The default for this field is None.
94+
total_dft_energy_per_formula_unit: Optional[float]
95+
It has to be given per formula unit (as a result in corresponding Doc).
96+
Instead of recomputing the energy of the bulk structure every time, this
97+
value can also be provided in eV. If it is provided, the static run will be
98+
skipped. This energy is the typical output dft energy of the dft workflow.
99+
No conversion needed. It is set to 0 by default.
100+
supercell_matrix: Optional[Matrix3D]
101+
Instead of min_length, also a supercell_matrix can be given, e.g.
102+
[[1.0,0.0,0.0],[0.0,1.0,0.0],[0.0,0.0,1.0]]. By default, this matrix is
103+
set to None.
104+
temperature: float
105+
The temperature for the anharmonicity calculation. The default temp is 300.
106+
one_shot_approx: bool
107+
If true, finds the one shot approximation of sigma^A and if false, finds
108+
the full sigma^A. The default is True.
109+
seed: Optional[int]
110+
Seed to use for the random number generator
111+
(only used if one_shot_approx == False). Set to None by default.
112+
element_resolved: bool
113+
If true, calculate the atom-resolved sigma^A. This is false by default.
114+
mode_resolved: bool
115+
If true, calculate the mode-resolved sigma^A. This is false by default.
116+
site_resolved: bool
117+
If true, resolve sigma^A to the different sites.
118+
Default is false.
119+
n_samples: int
120+
Number of times displaced structures are sampled.
121+
Must be >= 1 and cannot be used when one_shot_approx == True.
122+
It is set to 1 by default.
123+
124+
Returns
125+
-------
126+
Flow
127+
The workflow for the anharmonicity calculations
128+
"""
129+
phonon_flow = self.phonon_maker.make(
130+
structure,
131+
prev_dir,
132+
born,
133+
epsilon_static,
134+
total_dft_energy_per_formula_unit,
135+
supercell_matrix,
136+
)
137+
138+
phonon_doc = phonon_flow.output
139+
anharmon_flow = self.make_from_phonon_doc(
140+
phonon_doc,
141+
prev_dir,
142+
temperature,
143+
one_shot_approx,
144+
seed,
145+
element_resolved,
146+
mode_resolved,
147+
site_resolved,
148+
n_samples,
149+
)
150+
151+
results = store_results(
152+
sigma_dict=anharmon_flow.output,
153+
phonon_doc=phonon_flow.output,
154+
one_shot=one_shot_approx,
155+
temp=temperature,
156+
n_samples=n_samples,
157+
seed=seed,
158+
)
159+
160+
jobs = [phonon_flow, anharmon_flow, results]
161+
return Flow(jobs, results.output)
162+
163+
def make_from_phonon_doc(
164+
self,
165+
phonon_doc: PhononBSDOSDoc,
166+
prev_dir: str | Path | None = None,
167+
temperature: float = 300,
168+
one_shot_approx: bool = True,
169+
seed: int | None = None,
170+
element_resolved: bool = False,
171+
mode_resolved: bool = False,
172+
site_resolved: bool = False,
173+
n_samples: int = 1,
174+
) -> Flow:
175+
"""Create an anharmonicity workflow from a phonon calculation.
176+
177+
Parameters
178+
----------
179+
phonon_doc: PhononBSDOSDoc
180+
The document to get the anharmonicity for
181+
prev_dir: Optional[str | Path]
182+
A previous calculation directory to use for copying outputs.
183+
Default is None.
184+
temperature: float
185+
The temperature for the anharmonicity calculation. Default is 300.
186+
one_shot_approx: bool
187+
If true, finds the one shot approximation of sigma^A and if false,
188+
finds the full sigma^A. The default is True.
189+
seed: Optional[int]
190+
Seed to use for the random number generator
191+
(only used if one_shot_approx == False). Default is None.
192+
element_resolved: bool
193+
If true, calculate the atom-resolved sigma^A. This is false by default.
194+
mode_resolved: bool
195+
If true, calculate the mode-resolved sigma^A. This is false by default.
196+
site_resolved: bool
197+
If true, resolve sigma^A to the different sites.
198+
Default is false.
199+
n_samples: int
200+
Number of times displaced structures are sampled.
201+
Must be >= 1 and cannot be used when one_shot_approx == True.
202+
It is set to 1 by default.
203+
204+
Returns
205+
-------
206+
Flow
207+
The anharmonicity quantification workflow
208+
"""
209+
# KB: Not always an error. There could be negative acoustic modes from
210+
# unconverged phonon calculations.
211+
if phonon_doc.has_imaginary_modes:
212+
warn(
213+
"The phonon model has imaginary modes, sampling maybe incorrect.",
214+
stacklevel=1,
215+
)
216+
217+
jobs = []
218+
phonon_supercell_job = get_phonon_supercell(
219+
phonon_doc.structure,
220+
phonon_doc.supercell_matrix,
221+
)
222+
jobs.append(phonon_supercell_job)
223+
224+
phonon_supercell = phonon_supercell_job.output
225+
226+
displace_supercell = displace_structure(
227+
phonon_supercell=phonon_supercell,
228+
force_constants=phonon_doc.force_constants,
229+
temp=temperature,
230+
one_shot=one_shot_approx,
231+
seed=seed,
232+
n_samples=n_samples,
233+
)
234+
jobs.append(displace_supercell)
235+
236+
force_eval_maker = self.phonon_maker.phonon_displacement_maker
237+
displacement_calcs = run_displacements(
238+
displacements=displace_supercell.output,
239+
phonon_supercell=phonon_supercell,
240+
force_eval_maker=force_eval_maker,
241+
socket=self.phonon_maker.socket,
242+
prev_dir_argname=self.prev_calc_dir_argname,
243+
prev_dir=prev_dir,
244+
)
245+
jobs.append(displacement_calcs)
246+
247+
# Get DFT and harmonic forces
248+
force_calcs = get_forces(
249+
phonon_doc.force_constants,
250+
phonon_supercell,
251+
displacement_calcs.output,
252+
)
253+
jobs.append(force_calcs)
254+
255+
# Calculate all desired sigma^A types
256+
sigma_calcs = get_sigmas(
257+
force_calcs.output[0],
258+
force_calcs.output[1],
259+
phonon_doc.force_constants,
260+
phonon_supercell,
261+
one_shot_approx,
262+
element_resolved,
263+
mode_resolved,
264+
site_resolved,
265+
)
266+
jobs.append(sigma_calcs)
267+
sigma_a_vals = sigma_calcs.output
268+
269+
return Flow(jobs, sigma_a_vals)
270+
271+
@property
272+
@abstractmethod
273+
def prev_calc_dir_argname(self) -> str | None:
274+
"""Name of argument informing static maker of previous calculation directory.
275+
276+
As this differs between different DFT codes (e.g., VASP, CP2K), it
277+
has been left as a property to be implemented by the inheriting class.
278+
279+
Note: this is only applicable if a relax_maker is specified; i.e., two
280+
calculations are performed for each ordering (relax -> static)
281+
"""

0 commit comments

Comments
 (0)