matcalc 0.3.2__tar.gz → 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {matcalc-0.3.2/src/matcalc.egg-info → matcalc-0.3.3}/PKG-INFO +9 -4
  2. {matcalc-0.3.2 → matcalc-0.3.3}/README.md +7 -2
  3. {matcalc-0.3.2 → matcalc-0.3.3}/pyproject.toml +5 -3
  4. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/__init__.py +1 -0
  5. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_base.py +36 -7
  6. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_elasticity.py +5 -3
  7. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_eos.py +5 -6
  8. matcalc-0.3.3/src/matcalc/_neb.py +142 -0
  9. matcalc-0.3.3/src/matcalc/_phonon.py +222 -0
  10. matcalc-0.3.3/src/matcalc/_phonon3.py +237 -0
  11. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_qha.py +84 -37
  12. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_relaxation.py +5 -4
  13. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_stability.py +6 -5
  14. matcalc-0.3.3/src/matcalc/_surface.py +261 -0
  15. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/benchmark.py +6 -6
  16. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/utils.py +6 -3
  17. {matcalc-0.3.2 → matcalc-0.3.3/src/matcalc.egg-info}/PKG-INFO +9 -4
  18. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc.egg-info/SOURCES.txt +2 -0
  19. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc.egg-info/requires.txt +1 -1
  20. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_chain.py +9 -11
  21. matcalc-0.3.3/tests/test_neb.py +31 -0
  22. matcalc-0.3.3/tests/test_surface.py +70 -0
  23. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_utils.py +0 -2
  24. matcalc-0.3.2/src/matcalc/_neb.py +0 -119
  25. matcalc-0.3.2/src/matcalc/_phonon.py +0 -174
  26. matcalc-0.3.2/src/matcalc/_phonon3.py +0 -184
  27. matcalc-0.3.2/tests/test_neb.py +0 -29
  28. {matcalc-0.3.2 → matcalc-0.3.3}/LICENSE +0 -0
  29. {matcalc-0.3.2 → matcalc-0.3.3}/setup.cfg +0 -0
  30. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/_cli.py +0 -0
  31. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/config.py +0 -0
  32. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/elemental_refs/MatPES-PBE-Element-Refs.json.gz +0 -0
  33. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/elemental_refs/MatPES-r2SCAN-Element-Refs.json.gz +0 -0
  34. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/py.typed +0 -0
  35. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc/units.py +0 -0
  36. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc.egg-info/dependency_links.txt +0 -0
  37. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc.egg-info/entry_points.txt +0 -0
  38. {matcalc-0.3.2 → matcalc-0.3.3}/src/matcalc.egg-info/top_level.txt +0 -0
  39. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_benchmark.py +0 -0
  40. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_cli.py +0 -0
  41. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_elasticity.py +0 -0
  42. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_eos.py +0 -0
  43. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_phonon.py +0 -0
  44. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_phonon3.py +0 -0
  45. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_qha.py +0 -0
  46. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_relaxation.py +0 -0
  47. {matcalc-0.3.2 → matcalc-0.3.3}/tests/test_stability.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matcalc
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Calculators for materials properties from the potential energy surface.
5
5
  Author-email: Runze Liu <rul048@ucsd.edu>, Eliott Liu <elliottliu17@gmail.com>, Janosh Riebesell <janosh@lbl.gov>, Ji Qi <j1qi@ucsd.edu>, Shyue Ping Ong <ongsp@ucsd.edu>, Tsz Wai Ko <t1ko@ucsd.edu>
6
6
  Keywords: AI,deep learning,force field,graph,interatomic potential,machine learning,materials,property prediction,science
@@ -39,7 +39,7 @@ Requires-Dist: deepmd-kit>=3.0.2; extra == "deepmd"
39
39
  Provides-Extra: grace
40
40
  Requires-Dist: tensorpotential>=0.5.1; extra == "grace"
41
41
  Provides-Extra: maml
42
- Requires-Dist: maml>=2025.2.25; extra == "maml"
42
+ Requires-Dist: maml>=2025.4.3; extra == "maml"
43
43
  Provides-Extra: orb
44
44
  Requires-Dist: orb-models>=0.4.2; extra == "orb"
45
45
  Requires-Dist: pynanoflann>=0.10.0; extra == "orb"
@@ -96,12 +96,12 @@ from pymatgen.ext.matproj import MPRester
96
96
 
97
97
  mpr = MPRester()
98
98
  si = mpr.get_structure_by_material_id("mp-149")
99
- c = mtc.ElasticityCalc(mtc.load_up("TensorNet-MatPES-PBE-v2025.1-PES"), relax_structure=True)
99
+ c = mtc.ElasticityCalc("TensorNet-MatPES-PBE-v2025.1-PES", relax_structure=True)
100
100
  props = c.calc(si)
101
101
  print(f"K_VRH = {props['bulk_modulus_vrh'] * 160.2176621} GPa")
102
102
  ```
103
103
 
104
- The output is `K_VRH = 102.08363100102596 GPa`.
104
+ The calculated `K_VRH` is about 102 GPa, in reasonably good agreement with the experimental and DFT values.
105
105
 
106
106
  While we generally recommend users to specify exactly the model they would like to use, MatCalc provides useful
107
107
  (case-insensitive) aliases to our recommended models for PBE and r2SCAN predictions. These can be loaded using:
@@ -213,6 +213,10 @@ results.to_csv("benchmark_results.csv")
213
213
  These will usually take a long time to run. Running on HPC resources is recommended. Please set `n_samples` when
214
214
  initializing the benchmark to limit the number of calculations to do some testing before running the full benchmark.
215
215
 
216
+ ## Docker Images
217
+
218
+ Docker images with MatCalc and LAMMPS support are available at the [Materials Virtual Lab Docker Repository].
219
+
216
220
  ## Citing
217
221
 
218
222
  A manuscript on `matcalc` is currently in the works. In the meantime, please see [`citation.cff`](citation.cff) or the GitHub
@@ -223,3 +227,4 @@ sidebar for a BibTeX and APA citation.
223
227
  [MatPES]: https://matpes.ai
224
228
  [MatCalc]: https://matcalc.ai
225
229
  [ASE]: https://wiki.fysik.dtu.dk/ase/
230
+ [Materials Virtual Lab Docker Repository]: https://hub.docker.com/orgs/materialsvirtuallab/repositories
@@ -47,12 +47,12 @@ from pymatgen.ext.matproj import MPRester
47
47
 
48
48
  mpr = MPRester()
49
49
  si = mpr.get_structure_by_material_id("mp-149")
50
- c = mtc.ElasticityCalc(mtc.load_up("TensorNet-MatPES-PBE-v2025.1-PES"), relax_structure=True)
50
+ c = mtc.ElasticityCalc("TensorNet-MatPES-PBE-v2025.1-PES", relax_structure=True)
51
51
  props = c.calc(si)
52
52
  print(f"K_VRH = {props['bulk_modulus_vrh'] * 160.2176621} GPa")
53
53
  ```
54
54
 
55
- The output is `K_VRH = 102.08363100102596 GPa`.
55
+ The calculated `K_VRH` is about 102 GPa, in reasonably good agreement with the experimental and DFT values.
56
56
 
57
57
  While we generally recommend users to specify exactly the model they would like to use, MatCalc provides useful
58
58
  (case-insensitive) aliases to our recommended models for PBE and r2SCAN predictions. These can be loaded using:
@@ -164,6 +164,10 @@ results.to_csv("benchmark_results.csv")
164
164
  These will usually take a long time to run. Running on HPC resources is recommended. Please set `n_samples` when
165
165
  initializing the benchmark to limit the number of calculations to do some testing before running the full benchmark.
166
166
 
167
+ ## Docker Images
168
+
169
+ Docker images with MatCalc and LAMMPS support are available at the [Materials Virtual Lab Docker Repository].
170
+
167
171
  ## Citing
168
172
 
169
173
  A manuscript on `matcalc` is currently in the works. In the meantime, please see [`citation.cff`](citation.cff) or the GitHub
@@ -174,3 +178,4 @@ sidebar for a BibTeX and APA citation.
174
178
  [MatPES]: https://matpes.ai
175
179
  [MatCalc]: https://matcalc.ai
176
180
  [ASE]: https://wiki.fysik.dtu.dk/ase/
181
+ [Materials Virtual Lab Docker Repository]: https://hub.docker.com/orgs/materialsvirtuallab/repositories
@@ -45,7 +45,7 @@ dependencies = [
45
45
  "phonopy>=2.38.0",
46
46
  "phono3py>=3.15.0",
47
47
  ]
48
- version = "0.3.2"
48
+ version = "0.3.3"
49
49
 
50
50
  [project.optional-dependencies]
51
51
  phonon = ["seekpath"]
@@ -63,7 +63,7 @@ grace = [
63
63
  "tensorpotential>=0.5.1",
64
64
  ]
65
65
  maml = [
66
- "maml>=2025.2.25",
66
+ "maml>=2025.4.3",
67
67
  ]
68
68
  orb = [
69
69
  "orb-models>=0.4.2",
@@ -94,10 +94,11 @@ dev = [
94
94
  "coveralls>=4.0.1",
95
95
  "ipython>=8.34.0",
96
96
  "jupyter>=1.1.1",
97
- "maml>=2025.2.25",
97
+ "maml>=2025.4.3",
98
98
  "matgl>=1.2.3",
99
99
  "matminer>=0.9.3",
100
100
  "mp-api>=0.45.3",
101
+ "nbmake>=1.5.5",
101
102
  "pre-commit>=4.2.0",
102
103
  "pytest>=8.3.5",
103
104
  "pytest-cov>=6.0.0",
@@ -113,6 +114,7 @@ maintain = [
113
114
  "invoke>=2.2.0",
114
115
  "sphinx>=8.1.3",
115
116
  "sphinx-markdown-builder>=0.6.8",
117
+ "sphinx-rtd-theme>=3.0.2",
116
118
  ]
117
119
  test-models = [
118
120
  "mace-torch>=0.3.12",
@@ -18,6 +18,7 @@ from ._phonon3 import Phonon3Calc
18
18
  from ._qha import QHACalc
19
19
  from ._relaxation import RelaxCalc
20
20
  from ._stability import EnergeticsCalc
21
+ from ._surface import SurfaceCalc
21
22
  from .config import clear_cache
22
23
  from .utils import UNIVERSAL_CALCULATORS, PESCalculator
23
24
 
@@ -7,9 +7,12 @@ from typing import TYPE_CHECKING, Any
7
7
 
8
8
  from joblib import Parallel, delayed
9
9
 
10
+ from .utils import PESCalculator
11
+
10
12
  if TYPE_CHECKING:
11
13
  from collections.abc import Generator, Sequence
12
14
 
15
+ from ase.calculators.calculator import Calculator
13
16
  from pymatgen.core import Structure
14
17
 
15
18
 
@@ -25,6 +28,33 @@ class PropCalc(abc.ABC):
25
28
  on multiple structures using joblib.
26
29
  """
27
30
 
31
+ @property
32
+ def calculator(self) -> Calculator:
33
+ """
34
+ This method returns the Calculator object associated with the current instance.
35
+
36
+ Parameters:
37
+ None
38
+
39
+ Returns:
40
+ Calculator: The Calculator object associated with the current instance.
41
+ """
42
+ return self._pes_calculator
43
+
44
+ @calculator.setter
45
+ def calculator(self, val: str | Calculator) -> None:
46
+ """
47
+ Set the calculator for PES calculation.
48
+
49
+ Parameters:
50
+ val (str | Calculator): A string path to load a PESCalculator or a PESCalculator object.
51
+
52
+ Return:
53
+ None
54
+
55
+ """
56
+ self._pes_calculator = PESCalculator.load_universal(val) if isinstance(val, str) else val
57
+
28
58
  @abc.abstractmethod
29
59
  def calc(self, structure: Structure | dict[str, Any]) -> dict[str, Any]:
30
60
  """
@@ -93,16 +123,15 @@ class PropCalc(abc.ABC):
93
123
  the `allow_errors` flag).
94
124
  """
95
125
  parallel = Parallel(n_jobs=n_jobs, return_as="generator", **kwargs)
96
- if allow_errors:
97
126
 
98
- def _func(s: Structure) -> dict | None:
99
- try:
100
- return self.calc(s)
101
- except Exception: # noqa: BLE001
127
+ def _func(s: Structure) -> dict | None:
128
+ try:
129
+ return self.calc(s)
130
+ except Exception as ex:
131
+ if allow_errors:
102
132
  return None
133
+ raise ex # noqa:TRY201
103
134
 
104
- else:
105
- _func = self.calc # type: ignore[assignment]
106
135
  return parallel(delayed(_func)(s) for s in structures)
107
136
 
108
137
 
@@ -49,7 +49,7 @@ class ElasticityCalc(PropCalc):
49
49
 
50
50
  def __init__(
51
51
  self,
52
- calculator: Calculator,
52
+ calculator: Calculator | str,
53
53
  *,
54
54
  norm_strains: Sequence[float] | float = (-0.01, -0.005, 0.005, 0.01),
55
55
  shear_strains: Sequence[float] | float = (-0.06, -0.03, 0.03, 0.06),
@@ -65,7 +65,9 @@ class ElasticityCalc(PropCalc):
65
65
  and control relaxation behavior for structures. Validates input parameters to ensure
66
66
  appropriate constraints are maintained.
67
67
 
68
- :param calculator: Calculator object used for performing calculations.
68
+ :param calculator: An ASE calculator object used to perform energy and force
69
+ calculations. If string is provided, the corresponding universal calculator is loaded.
70
+ :type calculator: Calculator | str
69
71
  :param norm_strains: Sequence of normalized strain values applied during deformation.
70
72
  Can also be a single float. Must not be empty or contain zero.
71
73
  :param shear_strains: Sequence of shear strain values applied during deformation.
@@ -81,7 +83,7 @@ class ElasticityCalc(PropCalc):
81
83
  :param relax_calc_kwargs: Optional dictionary containing keyword arguments for structure
82
84
  relaxation calculations.
83
85
  """
84
- self.calculator = calculator
86
+ self.calculator = calculator # type: ignore[assignment]
85
87
  self.norm_strains = tuple(np.array([1]) * np.asarray(norm_strains))
86
88
  self.shear_strains = tuple(np.array([1]) * np.asarray(shear_strains))
87
89
  if len(self.norm_strains) == 0:
@@ -49,7 +49,7 @@ class EOSCalc(PropCalc):
49
49
 
50
50
  def __init__(
51
51
  self,
52
- calculator: Calculator,
52
+ calculator: Calculator | str,
53
53
  *,
54
54
  optimizer: Optimizer | str = "FIRE",
55
55
  max_steps: int = 500,
@@ -65,10 +65,9 @@ class EOSCalc(PropCalc):
65
65
  simulation parameters, structural relaxation options, and optimizations
66
66
  with specified constraints and tolerances.
67
67
 
68
- :param calculator: The calculator object that handles the computation of
69
- forces, energies, and other related properties for the system being
70
- studied.
71
- :type calculator: Calculator
68
+ :param calculator: An ASE calculator object used to perform energy and force
69
+ calculations. If string is provided, the corresponding universal calculator is loaded.
70
+ :type calculator: Calculator | str
72
71
  :param optimizer: The optimization algorithm used for structural relaxations
73
72
  or energy minimizations. Can be an optimizer object or the string name
74
73
  of the algorithm. Default is "FIRE".
@@ -92,7 +91,7 @@ class EOSCalc(PropCalc):
92
91
  relaxation calculation process. Default is None.
93
92
  :type relax_calc_kwargs: dict | None, optional
94
93
  """
95
- self.calculator = calculator
94
+ self.calculator = calculator # type: ignore[assignment]
96
95
  self.optimizer = optimizer
97
96
  self.relax_structure = relax_structure
98
97
  self.n_points = n_points
@@ -0,0 +1,142 @@
1
+ """NEB calculations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from ase.io import Trajectory
9
+ from ase.mep import NEBTools
10
+ from ase.neb import NEB
11
+ from ase.utils.forcecurve import fit_images
12
+ from pymatgen.core import Structure
13
+ from pymatgen.io.ase import AseAtomsAdaptor
14
+
15
+ from ._base import PropCalc
16
+ from .utils import get_ase_optimizer
17
+
18
+ if TYPE_CHECKING:
19
+ from ase import Atoms
20
+ from ase.calculators.calculator import Calculator
21
+ from ase.optimize.optimize import Optimizer
22
+
23
+
24
+ class NEBCalc(PropCalc):
25
+ """NEB calculator."""
26
+
27
+ def __init__(
28
+ self,
29
+ calculator: Calculator | str,
30
+ *,
31
+ optimizer: str | Optimizer = "BFGS",
32
+ traj_folder: str | None = None,
33
+ interval: int = 1,
34
+ climb: bool = True,
35
+ fmax: float = 0.1,
36
+ max_steps: int = 1000,
37
+ **kwargs: Any,
38
+ ) -> None:
39
+ """Initialize the instance of the class.
40
+
41
+ Parameters:
42
+ calculator (Calculator | str): An ASE calculator object used to perform energy and force
43
+ calculations. If string is provided, the corresponding universal calculator is loaded.
44
+ optimizer (str | Optimizer, optional): The optimization algorithm to use. Defaults to "BFGS".
45
+ traj_folder (str | None, optional): The folder to save trajectory information. Defaults to None.
46
+ interval (int, optional): The interval for recording trajectory information. Defaults to 1.
47
+ climb (bool, optional): Whether to perform climbing image nudged elastic band (CI-NEB). Defaults to True.
48
+ fmax (float, optional): The maximum force tolerance for convergence. Defaults to 0.1.
49
+ max_steps (int, optional): The maximum number of optimization steps. Defaults to 1000.
50
+ **kwargs (Any): Additional keyword arguments.
51
+
52
+ Returns:
53
+ None
54
+ """
55
+ self.calculator = calculator # type: ignore[assignment]
56
+
57
+ self.traj_folder = traj_folder
58
+ self.interval = interval
59
+ self.climb = climb
60
+ self.optimizer = get_ase_optimizer(optimizer)
61
+ self.fmax = fmax
62
+ self.max_steps = max_steps
63
+ self.kwargs = kwargs
64
+
65
+ def calc_images(
66
+ self,
67
+ start_struct: Structure,
68
+ end_struct: Structure,
69
+ *,
70
+ n_images: int = 7,
71
+ interpolate_lattices: bool = False,
72
+ autosort_tol: float = 0.5,
73
+ ) -> dict[str, Any]:
74
+ """Calculate NEB images between given start and end structures.
75
+
76
+ Parameters:
77
+ start_struct (Structure): Initial structure.
78
+ end_struct (Structure): Final structure.
79
+ n_images (int): Number of images to calculate (default is 7).
80
+ interpolate_lattices (bool): Whether to interpolate lattices between start and end structures (default is
81
+ False).
82
+ autosort_tol (float): Tolerance for autosorting the images (default is 0.5).
83
+
84
+ Returns:
85
+ NEBCalc: NEB calculation object containing the interpolated images.
86
+ """
87
+ images = start_struct.interpolate(
88
+ end_struct,
89
+ nimages=n_images + 1,
90
+ interpolate_lattices=interpolate_lattices,
91
+ pbc=False,
92
+ autosort_tol=autosort_tol,
93
+ )
94
+ return self.calc({f"image{i:02d}": s for i, s in enumerate(images)})
95
+
96
+ def calc(
97
+ self,
98
+ structure: Structure | dict[str, Any],
99
+ ) -> dict[str, Any]:
100
+ """Calculate the energy barrier using the nudged elastic band method.
101
+
102
+ Parameters:
103
+ - structure: A dictionary containing the images with keys 'image0', 'image1', etc. Must be of type dict.
104
+
105
+ Returns:
106
+ dict:
107
+ - "barrier" (float): The energy barrier of the reaction pathway.
108
+ - "force" (float): The force exerted on the atoms during the NEB calculation.
109
+ - "mep" (dict): a dictionary containing the images and their respective energies.
110
+ """
111
+ if not isinstance(structure, dict):
112
+ raise ValueError( # noqa:TRY004
113
+ "For NEB calculations, structure must be a dict containing the images with keys image00, image01, etc."
114
+ )
115
+ images: list[Atoms] = []
116
+ for _, image in sorted(structure.items(), key=lambda x: x[0]):
117
+ atoms = image.to_ase_atoms() if isinstance(image, Structure) else image
118
+ atoms.calc = self.calculator
119
+ images.append(atoms)
120
+
121
+ self.neb = NEB(images, climb=self.climb, allow_shared_calculator=True, **self.kwargs)
122
+ optimizer = self.optimizer(self.neb) # type:ignore[operator]
123
+ if self.traj_folder is not None:
124
+ os.makedirs(self.traj_folder, exist_ok=True)
125
+ for idx, img in enumerate(images):
126
+ optimizer.attach(
127
+ Trajectory(f"{self.traj_folder}/image-{idx}.traj", "w", img),
128
+ interval=self.interval,
129
+ )
130
+
131
+ optimizer.run(fmax=self.fmax, steps=self.max_steps)
132
+ neb_tool = NEBTools(self.neb.images)
133
+ data = neb_tool.get_barrier() # add structures
134
+ result = {"barrier": data[0], "force": data[1]}
135
+
136
+ energies = fit_images(self.neb.images).energies
137
+ mep = {
138
+ f"image{i:02d}": {"structure": AseAtomsAdaptor.get_structure(image), "energy": energy}
139
+ for i, (image, energy) in enumerate(zip(self.neb.images, energies, strict=False))
140
+ }
141
+ result["mep"] = mep
142
+ return result
@@ -0,0 +1,222 @@
1
+ """Calculator for phonon properties."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import phonopy
8
+ from phonopy.file_IO import write_FORCE_CONSTANTS as write_force_constants
9
+ from pymatgen.io.ase import AseAtomsAdaptor
10
+ from pymatgen.io.phonopy import get_phonopy_structure, get_pmg_structure
11
+
12
+ from ._base import PropCalc
13
+ from ._relaxation import RelaxCalc
14
+
15
+ if TYPE_CHECKING:
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from ase.calculators.calculator import Calculator
20
+ from numpy.typing import ArrayLike
21
+ from phonopy.structure.atoms import PhonopyAtoms
22
+ from pymatgen.core import Structure
23
+
24
+
25
+ class PhononCalc(PropCalc):
26
+ """
27
+ A class for phonon and thermal property calculations using phonopy.
28
+
29
+ The `PhononCalc` class extends the `PropCalc` class to provide
30
+ functionalities for calculating phonon properties and thermal properties
31
+ of a given structure using the phonopy library. It includes options for
32
+ structural relaxation before phonon property determination, as well as
33
+ methods to export calculated properties to various output files for
34
+ further analysis or visualization.
35
+
36
+ :ivar calculator: A calculator object or a string specifying the
37
+ computational backend to be used.
38
+ :type calculator: Calculator | str
39
+ :ivar atom_disp: Magnitude of atomic displacements for phonon
40
+ calculations.
41
+ :type atom_disp: float
42
+ :ivar supercell_matrix: Array defining the transformation matrix to
43
+ construct supercells for phonon calculations.
44
+ :type supercell_matrix: ArrayLike
45
+ :ivar t_step: Temperature step for thermal property calculations in
46
+ Kelvin.
47
+ :type t_step: float
48
+ :ivar t_max: Maximum temperature for thermal property calculations in
49
+ Kelvin.
50
+ :type t_max: float
51
+ :ivar t_min: Minimum temperature for thermal property calculations in
52
+ Kelvin.
53
+ :type t_min: float
54
+ :ivar fmax: Maximum force convergence criterion for structural relaxation.
55
+ :type fmax: float
56
+ :ivar optimizer: String specifying the optimizer type to be used for
57
+ structural relaxation.
58
+ :type optimizer: str
59
+ :ivar relax_structure: Boolean flag to determine whether to relax the
60
+ structure before phonon calculation.
61
+ :type relax_structure: bool
62
+ :ivar relax_calc_kwargs: Optional dictionary containing additional
63
+ arguments for the structural relaxation calculation.
64
+ :type relax_calc_kwargs: dict | None
65
+ :ivar write_force_constants: Path, boolean, or string specifying whether
66
+ to write the calculated force constants to an output file, and the
67
+ path or name of the file if applicable.
68
+ :type write_force_constants: bool | str | Path
69
+ :ivar write_band_structure: Path, boolean, or string specifying whether
70
+ to write the calculated phonon band structure to an output file,
71
+ and the path or name of the file if applicable.
72
+ :type write_band_structure: bool | str | Path
73
+ :ivar write_total_dos: Path, boolean, or string specifying whether to
74
+ write the calculated total density of states (DOS) to an output
75
+ file, and the path or name of the file if applicable.
76
+ :type write_total_dos: bool | str | Path
77
+ :ivar write_phonon: Path, boolean, or string specifying whether to write
78
+ the calculated phonon properties (e.g., phonon.yaml) to an output
79
+ file, and the path or name of the file if applicable.
80
+ :type write_phonon: bool | str | Path
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ calculator: Calculator | str,
86
+ *,
87
+ atom_disp: float = 0.015,
88
+ supercell_matrix: ArrayLike = ((2, 0, 0), (0, 2, 0), (0, 0, 2)),
89
+ t_step: float = 10,
90
+ t_max: float = 1000,
91
+ t_min: float = 0,
92
+ fmax: float = 0.1,
93
+ optimizer: str = "FIRE",
94
+ relax_structure: bool = True,
95
+ relax_calc_kwargs: dict | None = None,
96
+ write_force_constants: bool | str | Path = False,
97
+ write_band_structure: bool | str | Path = False,
98
+ write_total_dos: bool | str | Path = False,
99
+ write_phonon: bool | str | Path = True,
100
+ ) -> None:
101
+ """
102
+ Initializes the class with configuration for the phonon calculations. The initialization parameters control
103
+ the behavior of structural relaxation, thermal properties, force calculations, and output file generation.
104
+ The class allows for customization of key parameters to facilitate the study of material behaviors.
105
+
106
+ :param calculator: The calculator object or string name specifying the calculation backend to use.
107
+ :param atom_disp: Atom displacement to be used for finite difference calculation of force constants.
108
+ :param supercell_matrix: Transformation matrix to define the supercell for the calculation.
109
+ :param t_step: Temperature step for thermal property calculations.
110
+ :param t_max: Maximum temperature for thermal property calculations.
111
+ :param t_min: Minimum temperature for thermal property calculations.
112
+ :param fmax: Maximum force during structure relaxation, used as a convergence criterion.
113
+ :param optimizer: Name of the optimization algorithm for structural relaxation.
114
+ :param relax_structure: Flag to indicate whether structure relaxation should be performed before calculations.
115
+ :param relax_calc_kwargs: Additional keyword arguments for relaxation phase calculations.
116
+ :param write_force_constants: File path or boolean flag to write force constants.
117
+ Defaults to "force_constants".
118
+ :param write_band_structure: File path or boolean flag to write band structure data.
119
+ Defaults to "band_structure.yaml".
120
+ :param write_total_dos: File path or boolean flag to write total density of states (DOS) data.
121
+ Defaults to "total_dos.dat".
122
+ :param write_phonon: File path or boolean flag to write phonon data. Defaults to "phonon.yaml".
123
+ """
124
+ self.calculator = calculator # type: ignore[assignment]
125
+ self.atom_disp = atom_disp
126
+ self.supercell_matrix = supercell_matrix
127
+ self.t_step = t_step
128
+ self.t_max = t_max
129
+ self.t_min = t_min
130
+ self.fmax = fmax
131
+ self.optimizer = optimizer
132
+ self.relax_structure = relax_structure
133
+ self.relax_calc_kwargs = relax_calc_kwargs
134
+ self.write_force_constants = write_force_constants
135
+ self.write_band_structure = write_band_structure
136
+ self.write_total_dos = write_total_dos
137
+ self.write_phonon = write_phonon
138
+
139
+ # Set default paths for output files.
140
+ for key, val, default_path in (
141
+ ("write_force_constants", self.write_force_constants, "force_constants"),
142
+ ("write_band_structure", self.write_band_structure, "band_structure.yaml"),
143
+ ("write_total_dos", self.write_total_dos, "total_dos.dat"),
144
+ ("write_phonon", self.write_phonon, "phonon.yaml"),
145
+ ):
146
+ setattr(self, key, str({True: default_path, False: ""}.get(val, val))) # type: ignore[arg-type]
147
+
148
+ def calc(self, structure: Structure | dict[str, Any]) -> dict:
149
+ """Calculates thermal properties of Pymatgen structure with phonopy.
150
+
151
+ Args:
152
+ structure: Pymatgen structure.
153
+
154
+ Returns:
155
+ {
156
+ phonon: Phonopy object with force constants produced
157
+ thermal_properties:
158
+ {
159
+ temperatures: list of temperatures in Kelvin,
160
+ free_energy: list of Helmholtz free energies at corresponding temperatures in kJ/mol,
161
+ entropy: list of entropies at corresponding temperatures in J/K/mol,
162
+ heat_capacity: list of heat capacities at constant volume at corresponding temperatures in J/K/mol,
163
+ The units are originally documented in phonopy.
164
+ See phonopy.Phonopy.run_thermal_properties()
165
+ (https://github.com/phonopy/phonopy/blob/develop/phonopy/api_phonopy.py#L2591)
166
+ -> phonopy.phonon.thermal_properties.ThermalProperties.run()
167
+ (https://github.com/phonopy/phonopy/blob/develop/phonopy/phonon/thermal_properties.py#L498)
168
+ -> phonopy.phonon.thermal_properties.ThermalPropertiesBase.run_free_energy()
169
+ (https://github.com/phonopy/phonopy/blob/develop/phonopy/phonon/thermal_properties.py#L217)
170
+ phonopy.phonon.thermal_properties.ThermalPropertiesBase.run_entropy()
171
+ (https://github.com/phonopy/phonopy/blob/develop/phonopy/phonon/thermal_properties.py#L233)
172
+ phonopy.phonon.thermal_properties.ThermalPropertiesBase.run_heat_capacity()
173
+ (https://github.com/phonopy/phonopy/blob/develop/phonopy/phonon/thermal_properties.py#L225)
174
+ }
175
+ }
176
+ """
177
+ result = super().calc(structure)
178
+ structure_in: Structure = result["final_structure"]
179
+
180
+ if self.relax_structure:
181
+ relaxer = RelaxCalc(
182
+ self.calculator, fmax=self.fmax, optimizer=self.optimizer, **(self.relax_calc_kwargs or {})
183
+ )
184
+ result |= relaxer.calc(structure_in)
185
+ structure_in = result["final_structure"]
186
+ cell = get_phonopy_structure(structure_in)
187
+ phonon = phonopy.Phonopy(cell, self.supercell_matrix) # type: ignore[arg-type]
188
+ phonon.generate_displacements(distance=self.atom_disp)
189
+ disp_supercells = phonon.supercells_with_displacements
190
+ phonon.forces = [ # type: ignore[assignment]
191
+ _calc_forces(self.calculator, supercell)
192
+ for supercell in disp_supercells # type:ignore[union-attr]
193
+ if supercell is not None
194
+ ]
195
+ phonon.produce_force_constants()
196
+ phonon.run_mesh()
197
+ phonon.run_thermal_properties(t_step=self.t_step, t_max=self.t_max, t_min=self.t_min)
198
+ if self.write_force_constants:
199
+ write_force_constants(phonon.force_constants, filename=self.write_force_constants)
200
+ if self.write_band_structure:
201
+ phonon.auto_band_structure(write_yaml=True, filename=self.write_band_structure)
202
+ if self.write_total_dos:
203
+ phonon.auto_total_dos(write_dat=True, filename=self.write_total_dos)
204
+ if self.write_phonon:
205
+ phonon.save(filename=self.write_phonon)
206
+ return result | {"phonon": phonon, "thermal_properties": phonon.get_thermal_properties_dict()}
207
+
208
+
209
+ def _calc_forces(calculator: Calculator, supercell: PhonopyAtoms) -> ArrayLike:
210
+ """Helper to compute forces on a structure.
211
+
212
+ Args:
213
+ calculator: ASE Calculator
214
+ supercell: Supercell from phonopy.
215
+
216
+ Return:
217
+ forces
218
+ """
219
+ struct = get_pmg_structure(supercell)
220
+ atoms = AseAtomsAdaptor.get_atoms(struct)
221
+ atoms.calc = calculator
222
+ return atoms.get_forces()