rsspolymlp 0.3.3__tar.gz → 0.4.0__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.
- {rsspolymlp-0.3.3/src/rsspolymlp.egg-info → rsspolymlp-0.4.0}/PKG-INFO +16 -12
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/README.md +15 -11
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/pyproject.toml +1 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/eos.py +1 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/ghost_minima.py +9 -13
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/phase_analysis.py +95 -43
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/rss_summarize.py +58 -44
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/struct_match.py +67 -35
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/unique_struct.py +5 -5
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp.py +16 -15
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_devkit.py +29 -24
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_utils.py +68 -8
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp.py +25 -45
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp_devkit.py +41 -30
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp_plot.py +9 -12
- rsspolymlp-0.4.0/src/rsspolymlp/api/rsspolymlp_utils.py +185 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/atomic_energy.py +1 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/composition.py +2 -3
- rsspolymlp-0.4.0/src/rsspolymlp/common/interface_vasp.py +318 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/dataset/gen_mlp_dataset.py +37 -25
- rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/dataset/split_dataset.py +186 -0
- rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/model_selection/binary/gen_single_model.py +74 -0
- {rsspolymlp-0.3.3/src/rsspolymlp/mlp_dev/model_selection → rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/model_selection/elemental}/gen_single_model.py +3 -3
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/n_feature.py +16 -4
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/pypolymlp_gridsearch.py +3 -3
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/reduce_grid.py +7 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/polymlp_dev.py +2 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/eliminate_duplicates.py +53 -7
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/optimization_mlp.py +13 -7
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/example.py +2 -2
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/template.py +2 -2
- rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/opt_geometry_sscha.py +470 -0
- rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/opt_position_sscha.py +179 -0
- rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/run_opt_sscha.py +415 -0
- rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/sscha_property.py +279 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/spglib_utils.py +1 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/gen_script.py +3 -2
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/write_poscar.py +12 -1
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0/src/rsspolymlp.egg-info}/PKG-INFO +16 -12
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/SOURCES.txt +9 -9
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/tests/test_cli_help.py +0 -1
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/load_plot_data.py +0 -36
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/invert_and_swap.py +0 -106
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/irrep_position.py +0 -379
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/niggli.py +0 -10
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/struct_match.py +0 -264
- rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/utils.py +0 -83
- rsspolymlp-0.3.3/src/rsspolymlp/api/rsspolymlp_utils.py +0 -87
- rsspolymlp-0.3.3/src/rsspolymlp/mlp_dev/dataset/divide_dataset.py +0 -130
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/LICENSE +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/setup.cfg +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/chiral_spg.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/invert_and_swap.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/reduced_position.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_plot.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/convert_dict.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/property.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/dataset/compress_vasprun.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/estimate_cost.py +0 -0
- {rsspolymlp-0.3.3/src/rsspolymlp/mlp_dev/model_selection → rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/model_selection/elemental}/gen_hybrid_model.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/pareto_opt_mlp.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/load_logfile.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/random_struct.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/lammps_utils.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/custom_plt.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/make_plot.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/pymatgen_utils.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/__init__.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/api.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/gen_incar.py +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/dependency_links.txt +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/entry_points.txt +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/requires.txt +0 -0
- {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rsspolymlp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A framework for random structure search using polynomial MLPs
|
|
5
5
|
Author-email: Hayato Wakai <wakai@cms.mtl.kyoto-u.ac.jp>
|
|
6
6
|
Maintainer-email: Hayato Wakai <wakai@cms.mtl.kyoto-u.ac.jp>
|
|
@@ -35,17 +35,21 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
If you use `rsspolymlp` in your study, please cite the following articles.
|
|
37
37
|
|
|
38
|
-
“
|
|
38
|
+
“Systematic global structure search of bismuth-based binary systems under pressure using machine learning potentials”, [Phys. Rev. Materials 10, 093801 (2026)](https://doi.org/10.1103/k9sg-v7hl)
|
|
39
39
|
```
|
|
40
|
-
@article{
|
|
41
|
-
title=
|
|
42
|
-
author={Hayato
|
|
43
|
-
journal={
|
|
44
|
-
volume={
|
|
45
|
-
|
|
46
|
-
pages={
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
@article{Phys.Rev.Mater.10.093801,
|
|
41
|
+
title = {Systematic global structure search of bismuth-based binary systems under pressure using machine learning potentials},
|
|
42
|
+
author = {Wakai, Hayato and Ishiwata, Shintaro and Seko, Atsuto},
|
|
43
|
+
journal = {Phys. Rev. Mater.},
|
|
44
|
+
volume = {10},
|
|
45
|
+
issue = {9},
|
|
46
|
+
pages = {093801},
|
|
47
|
+
numpages = {28},
|
|
48
|
+
year = {2026},
|
|
49
|
+
month = {Sep},
|
|
50
|
+
publisher = {American Physical Society},
|
|
51
|
+
doi = {10.1103/k9sg-v7hl},
|
|
52
|
+
url = {https://link.aps.org/doi/10.1103/k9sg-v7hl}
|
|
49
53
|
}
|
|
50
54
|
```
|
|
51
55
|
|
|
@@ -93,7 +97,7 @@ pip install rsspolymlp
|
|
|
93
97
|
- Unique structure identification and RSS result summarization
|
|
94
98
|
- Ghost minimum structure elimination
|
|
95
99
|
- Phase stability analysis
|
|
96
|
-
- [Development kit for polynomial MLPs](docs/
|
|
100
|
+
- [Development kit for polynomial MLPs](docs/rsspolymlp_dev.md)
|
|
97
101
|
- MLP dataset generation
|
|
98
102
|
- DFT dataset division
|
|
99
103
|
- Polynomial MLP development
|
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
If you use `rsspolymlp` in your study, please cite the following articles.
|
|
6
6
|
|
|
7
|
-
“
|
|
7
|
+
“Systematic global structure search of bismuth-based binary systems under pressure using machine learning potentials”, [Phys. Rev. Materials 10, 093801 (2026)](https://doi.org/10.1103/k9sg-v7hl)
|
|
8
8
|
```
|
|
9
|
-
@article{
|
|
10
|
-
title=
|
|
11
|
-
author={Hayato
|
|
12
|
-
journal={
|
|
13
|
-
volume={
|
|
14
|
-
|
|
15
|
-
pages={
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
@article{Phys.Rev.Mater.10.093801,
|
|
10
|
+
title = {Systematic global structure search of bismuth-based binary systems under pressure using machine learning potentials},
|
|
11
|
+
author = {Wakai, Hayato and Ishiwata, Shintaro and Seko, Atsuto},
|
|
12
|
+
journal = {Phys. Rev. Mater.},
|
|
13
|
+
volume = {10},
|
|
14
|
+
issue = {9},
|
|
15
|
+
pages = {093801},
|
|
16
|
+
numpages = {28},
|
|
17
|
+
year = {2026},
|
|
18
|
+
month = {Sep},
|
|
19
|
+
publisher = {American Physical Society},
|
|
20
|
+
doi = {10.1103/k9sg-v7hl},
|
|
21
|
+
url = {https://link.aps.org/doi/10.1103/k9sg-v7hl}
|
|
18
22
|
}
|
|
19
23
|
```
|
|
20
24
|
|
|
@@ -62,7 +66,7 @@ pip install rsspolymlp
|
|
|
62
66
|
- Unique structure identification and RSS result summarization
|
|
63
67
|
- Ghost minimum structure elimination
|
|
64
68
|
- Phase stability analysis
|
|
65
|
-
- [Development kit for polynomial MLPs](docs/
|
|
69
|
+
- [Development kit for polynomial MLPs](docs/rsspolymlp_dev.md)
|
|
66
70
|
- MLP dataset generation
|
|
67
71
|
- DFT dataset division
|
|
68
72
|
- Polynomial MLP development
|
|
@@ -8,9 +8,8 @@ import shutil
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
from sklearn.cluster import KMeans
|
|
10
10
|
|
|
11
|
-
from pypolymlp.core.interface_vasp import Vasprun
|
|
12
11
|
from pypolymlp.core.units import EVtoGPa
|
|
13
|
-
from rsspolymlp.common.
|
|
12
|
+
from rsspolymlp.common.interface_vasp import parse_properties_from_vasprun
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def detect_ghost_minima(energies: np.array, distances: np.array):
|
|
@@ -165,7 +164,9 @@ def detect_actual_ghost_minima(dft_path):
|
|
|
165
164
|
vasprun_get = False
|
|
166
165
|
for vasprun in vasprun_paths:
|
|
167
166
|
try:
|
|
168
|
-
|
|
167
|
+
structure, (energy_dft, _, stress_dft) = parse_properties_from_vasprun(
|
|
168
|
+
vasprun, units="GPa"
|
|
169
|
+
)
|
|
169
170
|
vasprun_get = True
|
|
170
171
|
except Exception:
|
|
171
172
|
continue
|
|
@@ -182,22 +183,17 @@ def detect_actual_ghost_minima(dft_path):
|
|
|
182
183
|
)
|
|
183
184
|
continue
|
|
184
185
|
|
|
185
|
-
energy_dft = vaspobj.energy
|
|
186
|
-
structure = vaspobj.structure
|
|
187
|
-
for element in structure.elements:
|
|
188
|
-
energy_dft -= atomic_energy(element)
|
|
189
|
-
energy_dft /= len(structure.elements)
|
|
190
186
|
vol_per_atom = structure.volume / len(structure.elements)
|
|
191
187
|
|
|
192
|
-
# Subtract pressure term from MLP enthalpy
|
|
193
188
|
mlp_energy = res["energy"]
|
|
194
189
|
mlp_energy -= pressure * vol_per_atom / EVtoGPa
|
|
190
|
+
energy_dft /= len(structure.elements)
|
|
191
|
+
e_diff = mlp_energy - energy_dft
|
|
195
192
|
|
|
196
|
-
stress_dft = [
|
|
193
|
+
stress_dft = [stress_dft[i][i] for i in range(3)]
|
|
197
194
|
press_diff = [pressure - stress_dft[i] for i in range(3)]
|
|
198
195
|
stress_diff = np.max(np.abs(press_diff)) * vol_per_atom / EVtoGPa
|
|
199
196
|
|
|
200
|
-
e_diff = mlp_energy - energy_dft
|
|
201
197
|
diff_all.append(
|
|
202
198
|
{
|
|
203
199
|
"energy_diff": e_diff,
|
|
@@ -221,8 +217,8 @@ def detect_actual_ghost_minima(dft_path):
|
|
|
221
217
|
|
|
222
218
|
print(f" - structure: {poscar}", file=f)
|
|
223
219
|
if not delta_e == "null":
|
|
224
|
-
print(f" energy_diff_meV_per_atom: {delta_e*1000:.3f}", file=f)
|
|
225
|
-
print(f" stress_diff_meV_per_atom: {delta_s*1000:.3f}", file=f)
|
|
220
|
+
print(f" energy_diff_meV_per_atom: {delta_e * 1000:.3f}", file=f)
|
|
221
|
+
print(f" stress_diff_meV_per_atom: {delta_s * 1000:.3f}", file=f)
|
|
226
222
|
e_threshold = -0.1 # unit: eV/atom
|
|
227
223
|
s_threshold = 0.5
|
|
228
224
|
if delta_e < e_threshold or delta_s > s_threshold:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ast
|
|
1
2
|
import json
|
|
2
3
|
import os
|
|
3
4
|
from collections import defaultdict
|
|
@@ -6,9 +7,8 @@ import numpy as np
|
|
|
6
7
|
import yaml
|
|
7
8
|
from scipy.spatial import ConvexHull
|
|
8
9
|
|
|
9
|
-
from pypolymlp.core.interface_vasp import Vasprun
|
|
10
|
-
from rsspolymlp.common.atomic_energy import atomic_energy
|
|
11
10
|
from rsspolymlp.common.composition import compute_composition
|
|
11
|
+
from rsspolymlp.common.interface_vasp import parse_properties_from_vasprun
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ConvexHullAnalyzer:
|
|
@@ -18,6 +18,7 @@ class ConvexHullAnalyzer:
|
|
|
18
18
|
self.elements = np.array(elements)
|
|
19
19
|
# Initialize per-composition storage
|
|
20
20
|
self._composition_data = {}
|
|
21
|
+
self.endmember_energies = None
|
|
21
22
|
# Convex hull object
|
|
22
23
|
self.hull = None
|
|
23
24
|
# Results for global minima on the hull
|
|
@@ -33,9 +34,9 @@ class ConvexHullAnalyzer:
|
|
|
33
34
|
def composition_data(self, data):
|
|
34
35
|
"""
|
|
35
36
|
composition_data maps each composition ratio (tuple) to a dict with keys:
|
|
36
|
-
'energy': np.ndarray of energies,
|
|
37
|
-
'input_path': np.ndarray of file paths,
|
|
38
|
-
'struct_tag': np.ndarray of structure tags.
|
|
37
|
+
- 'energy': np.ndarray of energies,
|
|
38
|
+
- 'input_path': np.ndarray of file paths,
|
|
39
|
+
- 'struct_tag': np.ndarray of structure tags.
|
|
39
40
|
"""
|
|
40
41
|
if not isinstance(data, dict):
|
|
41
42
|
raise TypeError("composition_data must be a dictionary.")
|
|
@@ -60,53 +61,53 @@ class ConvexHullAnalyzer:
|
|
|
60
61
|
self._composition_data = data
|
|
61
62
|
|
|
62
63
|
def run_analysis(self):
|
|
63
|
-
"""
|
|
64
|
+
"""
|
|
65
|
+
Construct the convex hull, compute formation energies, and evaluate energies above the hull.
|
|
66
|
+
The convex-hull results are stored in the following attributes:
|
|
67
|
+
- 'self.hull_energies': formation energies of structures on the hull
|
|
68
|
+
- 'self.hull_compositions': compositions of structures on the hull
|
|
69
|
+
- 'self.hull_paths': paths corresponding to structures on the hull
|
|
70
|
+
self._composition_data maps each composition ratio to a dictionary containing:
|
|
71
|
+
- 'formation_e': np.ndarray of formation energies
|
|
72
|
+
- 'fe_above_ch': np.ndarray of energies above the convex hull
|
|
73
|
+
"""
|
|
64
74
|
if len(self.elements) == 0:
|
|
65
75
|
raise ValueError("elements is none. Please set elements.")
|
|
66
76
|
if not self._composition_data:
|
|
67
77
|
raise ValueError("composition_data is empty. Please set results first.")
|
|
68
|
-
|
|
78
|
+
|
|
79
|
+
self.set_endmember_energies()
|
|
69
80
|
self.compute_convex_hull()
|
|
81
|
+
self.compute_formation_energies()
|
|
70
82
|
self.compute_fe_above_ch()
|
|
71
83
|
|
|
72
|
-
def
|
|
73
|
-
"""Compute formation energy for each structure relative to pure elements."""
|
|
74
|
-
# Sort compositions for reproducible output
|
|
75
|
-
comps = np.array(list(self._composition_data.keys()))
|
|
76
|
-
order = np.lexsort(comps.T)
|
|
77
|
-
sorted_keys = [tuple(comps[i].tolist()) for i in order]
|
|
78
|
-
self._composition_data = {k: self._composition_data[k] for k in sorted_keys}
|
|
79
|
-
|
|
84
|
+
def set_endmember_energies(self):
|
|
80
85
|
# Reference energies from pure elements (where any ratio == 1)
|
|
86
|
+
os.makedirs("phase_analysis/data", exist_ok=True)
|
|
87
|
+
comps = np.array(list(self._composition_data.keys()))
|
|
81
88
|
pure = comps[np.any(comps == 1, axis=1)]
|
|
82
89
|
pure_sorted = sorted(pure, key=lambda x: np.argmax(x))
|
|
83
|
-
|
|
90
|
+
self.endmember_energies = [
|
|
84
91
|
np.min(self._composition_data[tuple(r)]["energy"]) for r in pure_sorted
|
|
85
92
|
]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# Calculate and store formation energies
|
|
89
|
-
for ratio, data in self._composition_data.items():
|
|
90
|
-
formation = data["energy"] - np.dot(ref_energies, np.array(ratio))
|
|
91
|
-
data["formation_e"] = formation
|
|
92
|
-
|
|
93
|
-
# Save JSON and elements
|
|
94
|
-
os.makedirs("phase_analysis/data", exist_ok=True)
|
|
95
|
-
serial = {
|
|
96
|
-
str(r): {k: v.tolist() for k, v in d.items()}
|
|
97
|
-
for r, d in self._composition_data.items()
|
|
98
|
-
}
|
|
99
|
-
with open("phase_analysis/data/composition_data.json", "w") as f:
|
|
100
|
-
json.dump(serial, f)
|
|
101
|
-
np.save("phase_analysis/data/elements.npy", self.elements)
|
|
93
|
+
self.endmember_energies = np.array(self.endmember_energies)
|
|
102
94
|
|
|
103
95
|
def compute_convex_hull(self):
|
|
104
96
|
"""Build the convex hull and extract vertex compositions and energies."""
|
|
97
|
+
os.makedirs("phase_analysis/data", exist_ok=True)
|
|
98
|
+
# Sort compositions for reproducible output
|
|
99
|
+
comps = np.array(list(self._composition_data.keys()))
|
|
100
|
+
order = np.lexsort(comps.T)
|
|
101
|
+
sorted_keys = [tuple(comps[i].tolist()) for i in order]
|
|
102
|
+
self._composition_data = {k: self._composition_data[k] for k in sorted_keys}
|
|
103
|
+
|
|
105
104
|
comps, min_e, paths = [], [], []
|
|
106
105
|
for ratio, data in self._composition_data.items():
|
|
107
106
|
comps.append(ratio)
|
|
108
|
-
idx = np.argmin(data["
|
|
109
|
-
min_e.append(
|
|
107
|
+
idx = np.argmin(data["energy"])
|
|
108
|
+
min_e.append(
|
|
109
|
+
data["energy"][idx] - np.dot(self.endmember_energies, np.array(ratio))
|
|
110
|
+
)
|
|
110
111
|
paths.append(data["input_path"][idx])
|
|
111
112
|
|
|
112
113
|
comp_arr = np.array(comps)
|
|
@@ -140,10 +141,28 @@ class ConvexHullAnalyzer:
|
|
|
140
141
|
f.write(f" formation_e: {energy}\n")
|
|
141
142
|
|
|
142
143
|
# Save NumPy arrays
|
|
143
|
-
os.makedirs("phase_analysis/data", exist_ok=True)
|
|
144
144
|
np.save("phase_analysis/data/hull_e.npy", self.hull_energies)
|
|
145
145
|
np.save("phase_analysis/data/hull_comp.npy", self.hull_compositions)
|
|
146
146
|
|
|
147
|
+
def compute_formation_energies(self, json_output=True):
|
|
148
|
+
"""Compute formation energy for each structure relative to pure elements."""
|
|
149
|
+
# Calculate and store formation energies
|
|
150
|
+
for ratio, data in self._composition_data.items():
|
|
151
|
+
formation = data["energy"] - np.dot(
|
|
152
|
+
self.endmember_energies, np.array(ratio)
|
|
153
|
+
)
|
|
154
|
+
data["formation_e"] = formation
|
|
155
|
+
|
|
156
|
+
# Save JSON and elements
|
|
157
|
+
if json_output:
|
|
158
|
+
serial = {
|
|
159
|
+
str(r): {k: v.tolist() for k, v in d.items()}
|
|
160
|
+
for r, d in self._composition_data.items()
|
|
161
|
+
}
|
|
162
|
+
with open("phase_analysis/data/composition_data.json", "w") as f:
|
|
163
|
+
json.dump(serial, f)
|
|
164
|
+
np.save("phase_analysis/data/elements.npy", self.elements)
|
|
165
|
+
|
|
147
166
|
def compute_fe_above_ch(self):
|
|
148
167
|
"""Compute each structure's energy above the convex hull."""
|
|
149
168
|
for ratio, data in self._composition_data.items():
|
|
@@ -227,9 +246,9 @@ class ConvexHullAnalyzer:
|
|
|
227
246
|
def parse_results(self, input_paths, ghost_minima_file=None, parse_vasp=False):
|
|
228
247
|
"""
|
|
229
248
|
Populate composition_data by parsing RSS JSON or VASP vasprun.xml outputs.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
input_paths: List of RSS JSON files or VASP directories.
|
|
250
|
+
ghost_minima_file: YAML file listing ghost minima to exclude (RSS only).
|
|
251
|
+
parse_vasp: If True, parse VASP outputs; otherwise parse RSS JSON.
|
|
233
252
|
"""
|
|
234
253
|
if parse_vasp:
|
|
235
254
|
self._parse_vasp_results(input_paths)
|
|
@@ -299,16 +318,17 @@ class ConvexHullAnalyzer:
|
|
|
299
318
|
for dft_path in input_paths:
|
|
300
319
|
vasprun_path = f"{dft_path}/vasprun.xml"
|
|
301
320
|
try:
|
|
302
|
-
|
|
321
|
+
structure, (energy_dft, _, _) = parse_properties_from_vasprun(
|
|
322
|
+
vasprun_path
|
|
323
|
+
)
|
|
303
324
|
except Exception:
|
|
304
325
|
print(vasprun_path, "failed")
|
|
305
326
|
continue
|
|
306
327
|
|
|
307
|
-
energy_dft = vaspobj.energy
|
|
308
|
-
structure = vaspobj.structure
|
|
309
|
-
for element in structure.elements:
|
|
310
|
-
energy_dft -= atomic_energy(element)
|
|
311
328
|
energy_dft /= len(structure.elements)
|
|
329
|
+
if energy_dft < -15:
|
|
330
|
+
print(vasprun_path, "exhibits very low energy:", energy_dft, "eV/atom")
|
|
331
|
+
continue
|
|
312
332
|
|
|
313
333
|
comp_res = compute_composition(structure.elements, self.elements)
|
|
314
334
|
comp_ratio = tuple(
|
|
@@ -338,3 +358,35 @@ class ConvexHullAnalyzer:
|
|
|
338
358
|
}
|
|
339
359
|
|
|
340
360
|
self._composition_data = dft_dict_array
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def load_convexhull_data(path="./phase_analysis", threshold=None):
|
|
364
|
+
res = {}
|
|
365
|
+
|
|
366
|
+
res["elements"] = np.load(f"{path}/data/elements.npy")
|
|
367
|
+
res["hull_comp"] = np.load(f"{path}/data/hull_comp.npy")
|
|
368
|
+
res["hull_e"] = np.load(f"{path}/data/hull_e.npy")
|
|
369
|
+
with open(f"{path}/data/composition_data.json", "r") as f:
|
|
370
|
+
data = json.load(f)
|
|
371
|
+
res["composition_data"] = convert_json_to_ndarray(data)
|
|
372
|
+
|
|
373
|
+
res["not_near_ch"] = None
|
|
374
|
+
res["near_ch"] = None
|
|
375
|
+
if threshold is not None:
|
|
376
|
+
thre = float(threshold)
|
|
377
|
+
with open(f"{path}/threshold_{thre}meV/not_near_ch.json", "r") as f:
|
|
378
|
+
data1 = json.load(f)
|
|
379
|
+
with open(f"{path}/threshold_{thre}meV/near_ch.json", "r") as f:
|
|
380
|
+
data2 = json.load(f)
|
|
381
|
+
res["not_near_ch"] = convert_json_to_ndarray(data1)
|
|
382
|
+
res["near_ch"] = convert_json_to_ndarray(data2)
|
|
383
|
+
|
|
384
|
+
return res
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def convert_json_to_ndarray(data):
|
|
388
|
+
converted = {
|
|
389
|
+
ast.literal_eval(k): {key: np.array(val) for key, val in v.items()}
|
|
390
|
+
for k, v in data.items()
|
|
391
|
+
}
|
|
392
|
+
return converted
|
|
@@ -9,7 +9,6 @@ from time import time
|
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
|
-
from pypolymlp.core.interface_vasp import Vasprun
|
|
13
12
|
from pypolymlp.utils.vasp_utils import write_poscar_file
|
|
14
13
|
from rsspolymlp.analysis.ghost_minima import detect_ghost_minima
|
|
15
14
|
from rsspolymlp.analysis.unique_struct import (
|
|
@@ -18,9 +17,9 @@ from rsspolymlp.analysis.unique_struct import (
|
|
|
18
17
|
log_all_unique_structures,
|
|
19
18
|
log_unique_structures,
|
|
20
19
|
)
|
|
21
|
-
from rsspolymlp.common.atomic_energy import atomic_energy
|
|
22
20
|
from rsspolymlp.common.composition import compute_composition
|
|
23
21
|
from rsspolymlp.common.convert_dict import polymlp_struct_from_dict
|
|
22
|
+
from rsspolymlp.common.interface_vasp import Vasprun, parse_properties_from_vasprun
|
|
24
23
|
from rsspolymlp.common.property import PropUtil
|
|
25
24
|
|
|
26
25
|
|
|
@@ -80,9 +79,7 @@ class RSSResultSummarizer:
|
|
|
80
79
|
parent_path_ref[composition_tag] = json_path
|
|
81
80
|
|
|
82
81
|
if not self.parse_vasp:
|
|
83
|
-
paths_same_comp, results_same_comp = self._parse_json_result(
|
|
84
|
-
parse_rss_result=True
|
|
85
|
-
)
|
|
82
|
+
paths_same_comp, results_same_comp = self._parse_json_result()
|
|
86
83
|
axis_tol = 0.03
|
|
87
84
|
pos_tol = 0.03
|
|
88
85
|
else:
|
|
@@ -134,19 +131,22 @@ class RSSResultSummarizer:
|
|
|
134
131
|
for ustr in unique_structs:
|
|
135
132
|
num_opt_str += len(list(ustr.dupstr_paths))
|
|
136
133
|
|
|
137
|
-
with open(composition_tag + ".yaml", "w") as f:
|
|
138
|
-
print("general_information:", file=f)
|
|
139
|
-
print(f" sorting_time_sec: {round(time_finish, 2)}", file=f)
|
|
140
|
-
print(f" pressure_GPa: {self.pressure}", file=f)
|
|
141
|
-
print(f" num_optimized_structs: {num_opt_str}", file=f)
|
|
142
|
-
print(f" num_unique_structs: {len(unique_structs)}", file=f)
|
|
143
|
-
print("", file=f)
|
|
144
|
-
|
|
145
134
|
energies = np.array([s.energy for s in unique_structs])
|
|
146
135
|
distances = np.array([s.least_distance for s in unique_structs])
|
|
147
136
|
|
|
148
137
|
sort_idx = np.argsort(energies)
|
|
149
138
|
unique_str_sorted = [unique_structs[i] for i in sort_idx]
|
|
139
|
+
if self.thresholds is not None:
|
|
140
|
+
e_min = None
|
|
141
|
+
for idx, u in enumerate(unique_str_sorted):
|
|
142
|
+
if e_min is None:
|
|
143
|
+
e_min = u.energy
|
|
144
|
+
if u.energy - e_min > self.thresholds[0] * 0.001:
|
|
145
|
+
unique_str_sorted = unique_str_sorted[:idx]
|
|
146
|
+
break
|
|
147
|
+
output_file = f"{composition_tag}_{self.thresholds[0]}"
|
|
148
|
+
else:
|
|
149
|
+
output_file = composition_tag
|
|
150
150
|
|
|
151
151
|
if not self.parse_vasp:
|
|
152
152
|
os.makedirs("ghost_minima", exist_ok=True)
|
|
@@ -162,19 +162,27 @@ class RSSResultSummarizer:
|
|
|
162
162
|
else:
|
|
163
163
|
is_ghost_minima = None
|
|
164
164
|
|
|
165
|
+
with open(output_file + ".yaml", "w") as f:
|
|
166
|
+
print("general_information:", file=f)
|
|
167
|
+
print(f" sorting_time_sec: {round(time_finish, 2)}", file=f)
|
|
168
|
+
print(f" pressure_GPa: {self.pressure}", file=f)
|
|
169
|
+
print(f" num_optimized_structs: {num_opt_str}", file=f)
|
|
170
|
+
print(f" num_unique_structs: {len(unique_structs)}", file=f)
|
|
171
|
+
print("", file=f)
|
|
172
|
+
|
|
165
173
|
rss_result_all = log_unique_structures(
|
|
166
|
-
|
|
174
|
+
output_file + ".yaml",
|
|
167
175
|
unique_str_sorted,
|
|
168
176
|
is_ghost_minima,
|
|
169
177
|
pressure=self.pressure,
|
|
170
178
|
)
|
|
171
179
|
|
|
172
|
-
with open(f"json/{
|
|
180
|
+
with open(f"json/{output_file}.json", "w") as f:
|
|
173
181
|
json.dump(rss_result_all, f)
|
|
174
182
|
|
|
175
183
|
if self.thresholds is not None or self.output_poscar is not False:
|
|
176
184
|
self.generate_poscars(
|
|
177
|
-
f"json/{
|
|
185
|
+
f"json/{output_file}.json",
|
|
178
186
|
thresholds=self.thresholds,
|
|
179
187
|
output_poscar=self.output_poscar,
|
|
180
188
|
)
|
|
@@ -187,16 +195,25 @@ class RSSResultSummarizer:
|
|
|
187
195
|
paths_same_comp, results_same_comp = self._parse_json_result()
|
|
188
196
|
|
|
189
197
|
for composition_tag, res_paths in paths_same_comp.items():
|
|
198
|
+
print(f"Composition {composition_tag}: summarizing...")
|
|
190
199
|
self.analyzer = UniqueStructureAnalyzer()
|
|
191
200
|
|
|
201
|
+
if not self.parse_vasp:
|
|
202
|
+
axis_tol = 0.03
|
|
203
|
+
pos_tol = 0.03
|
|
204
|
+
else:
|
|
205
|
+
axis_tol = 0.1
|
|
206
|
+
pos_tol = 0.1
|
|
207
|
+
|
|
192
208
|
time_start = time()
|
|
193
209
|
for res_path in res_paths:
|
|
210
|
+
print(f" - Processing result file (parent): {res_path}")
|
|
194
211
|
self.remove_duplicates_in_comp(
|
|
195
212
|
results_same_comp[composition_tag][res_path],
|
|
196
213
|
standardize_axis=True,
|
|
197
214
|
keep_unique=True,
|
|
198
|
-
axis_tol=
|
|
199
|
-
pos_tol=
|
|
215
|
+
axis_tol=axis_tol,
|
|
216
|
+
pos_tol=pos_tol,
|
|
200
217
|
)
|
|
201
218
|
time_finish = time() - time_start
|
|
202
219
|
|
|
@@ -333,7 +350,7 @@ class RSSResultSummarizer:
|
|
|
333
350
|
|
|
334
351
|
for res in rss_results:
|
|
335
352
|
if threshold is not None:
|
|
336
|
-
diff =
|
|
353
|
+
diff = res["energy"] - e_min
|
|
337
354
|
if diff * 1000 > threshold:
|
|
338
355
|
continue
|
|
339
356
|
|
|
@@ -348,11 +365,10 @@ class RSSResultSummarizer:
|
|
|
348
365
|
shutil.copy(poscar_path, dest)
|
|
349
366
|
else:
|
|
350
367
|
try:
|
|
351
|
-
|
|
368
|
+
polymlp_st = Vasprun(res["struct_path"]).structure
|
|
352
369
|
except Exception:
|
|
353
370
|
print(res["struct_path"], "failed")
|
|
354
371
|
continue
|
|
355
|
-
polymlp_st = vaspobj.structure
|
|
356
372
|
write_poscar_file(polymlp_st, filename=dest)
|
|
357
373
|
shutil.copy(res["struct_path"], dest_vasp)
|
|
358
374
|
else:
|
|
@@ -364,16 +380,20 @@ class RSSResultSummarizer:
|
|
|
364
380
|
|
|
365
381
|
return struct_counts
|
|
366
382
|
|
|
367
|
-
def _parse_json_result(self
|
|
383
|
+
def _parse_json_result(self):
|
|
368
384
|
|
|
369
385
|
def resolve_path(base: Path, p):
|
|
370
386
|
cwd = Path.cwd()
|
|
387
|
+
target = Path(p)
|
|
371
388
|
if p is None:
|
|
372
389
|
return None
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
390
|
+
if not self.parse_vasp:
|
|
391
|
+
p = Path(p)
|
|
392
|
+
target = (
|
|
393
|
+
base / p
|
|
394
|
+
if "opt_struct" in p.parts
|
|
395
|
+
else base / "opt_struct" / p.name
|
|
396
|
+
)
|
|
377
397
|
return os.path.relpath(target, start=cwd)
|
|
378
398
|
|
|
379
399
|
paths_same_comp = defaultdict(list)
|
|
@@ -382,15 +402,12 @@ class RSSResultSummarizer:
|
|
|
382
402
|
with open(path_name) as f:
|
|
383
403
|
loaded_dict = json.load(f)
|
|
384
404
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
r["structure"] = polymlp_struct_from_dict(r["structure"])
|
|
393
|
-
r["struct_no"] = None
|
|
405
|
+
base = Path(path_name).parents[1]
|
|
406
|
+
for r in loaded_dict["rss_results"]:
|
|
407
|
+
r["struct_path"] = resolve_path(base, r["struct_path"])
|
|
408
|
+
r["dupstr_paths"] = {resolve_path(base, p) for p in r["dupstr_paths"]}
|
|
409
|
+
r["structure"] = polymlp_struct_from_dict(r["structure"])
|
|
410
|
+
r["struct_no"] = None
|
|
394
411
|
|
|
395
412
|
target_elements = loaded_dict["elements"]
|
|
396
413
|
comp_ratio = tuple(loaded_dict["comp_ratio"])
|
|
@@ -421,24 +438,21 @@ class RSSResultSummarizer:
|
|
|
421
438
|
"dupstr_paths": None,
|
|
422
439
|
}
|
|
423
440
|
try:
|
|
424
|
-
|
|
441
|
+
polymlp_st, (energy_dft, _, _) = parse_properties_from_vasprun(
|
|
442
|
+
path_name + "/vasprun.xml",
|
|
443
|
+
)
|
|
425
444
|
except Exception:
|
|
426
445
|
print("ParseError:", path_name + "/vasprun.xml")
|
|
427
446
|
continue
|
|
428
447
|
|
|
429
|
-
polymlp_st = vaspobj.structure
|
|
430
|
-
objprop = PropUtil(polymlp_st.axis.T, polymlp_st.positions.T)
|
|
431
|
-
spg_list = objprop.analyze_space_group(polymlp_st.elements)
|
|
432
|
-
|
|
433
|
-
energy_dft = vaspobj.energy
|
|
434
|
-
for element in polymlp_st.elements:
|
|
435
|
-
energy_dft -= atomic_energy(element)
|
|
436
448
|
energy_dft /= len(polymlp_st.elements)
|
|
437
|
-
|
|
438
449
|
if energy_dft < -10:
|
|
439
450
|
print(path_name, "exhibits an unphysically low energy. Skipping.")
|
|
440
451
|
continue
|
|
441
452
|
|
|
453
|
+
objprop = PropUtil(polymlp_st.axis.T, polymlp_st.positions.T)
|
|
454
|
+
spg_list = objprop.analyze_space_group(polymlp_st.elements)
|
|
455
|
+
|
|
442
456
|
res_dict["struct_path"] = os.path.relpath(
|
|
443
457
|
path_name + "/vasprun.xml", os.getcwd()
|
|
444
458
|
)
|