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.
Files changed (83) hide show
  1. {rsspolymlp-0.3.3/src/rsspolymlp.egg-info → rsspolymlp-0.4.0}/PKG-INFO +16 -12
  2. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/README.md +15 -11
  3. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/pyproject.toml +1 -1
  4. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/eos.py +1 -1
  5. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/ghost_minima.py +9 -13
  6. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/phase_analysis.py +95 -43
  7. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/rss_summarize.py +58 -44
  8. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/struct_match.py +67 -35
  9. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/unique_struct.py +5 -5
  10. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp.py +16 -15
  11. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_devkit.py +29 -24
  12. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_utils.py +68 -8
  13. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp.py +25 -45
  14. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp_devkit.py +41 -30
  15. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/rsspolymlp_plot.py +9 -12
  16. rsspolymlp-0.4.0/src/rsspolymlp/api/rsspolymlp_utils.py +185 -0
  17. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/atomic_energy.py +1 -1
  18. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/composition.py +2 -3
  19. rsspolymlp-0.4.0/src/rsspolymlp/common/interface_vasp.py +318 -0
  20. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/dataset/gen_mlp_dataset.py +37 -25
  21. rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/dataset/split_dataset.py +186 -0
  22. rsspolymlp-0.4.0/src/rsspolymlp/mlp_dev/model_selection/binary/gen_single_model.py +74 -0
  23. {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
  24. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/n_feature.py +16 -4
  25. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/pypolymlp_gridsearch.py +3 -3
  26. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/model_selection/reduce_grid.py +7 -1
  27. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/polymlp_dev.py +2 -1
  28. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/eliminate_duplicates.py +53 -7
  29. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/optimization_mlp.py +13 -7
  30. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/example.py +2 -2
  31. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/template.py +2 -2
  32. rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/opt_geometry_sscha.py +470 -0
  33. rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/opt_position_sscha.py +179 -0
  34. rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/run_opt_sscha.py +415 -0
  35. rsspolymlp-0.4.0/src/rsspolymlp/utils/opt_sscha/sscha_property.py +279 -0
  36. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/spglib_utils.py +1 -1
  37. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/gen_script.py +3 -2
  38. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/write_poscar.py +12 -1
  39. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0/src/rsspolymlp.egg-info}/PKG-INFO +16 -12
  40. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/SOURCES.txt +9 -9
  41. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/tests/test_cli_help.py +0 -1
  42. rsspolymlp-0.3.3/src/rsspolymlp/analysis/load_plot_data.py +0 -36
  43. rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/invert_and_swap.py +0 -106
  44. rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/irrep_position.py +0 -379
  45. rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/niggli.py +0 -10
  46. rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/struct_match.py +0 -264
  47. rsspolymlp-0.3.3/src/rsspolymlp/analysis/struct_matcher/deprecated/utils.py +0 -83
  48. rsspolymlp-0.3.3/src/rsspolymlp/api/rsspolymlp_utils.py +0 -87
  49. rsspolymlp-0.3.3/src/rsspolymlp/mlp_dev/dataset/divide_dataset.py +0 -130
  50. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/LICENSE +0 -0
  51. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/setup.cfg +0 -0
  52. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/__init__.py +0 -0
  53. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/__init__.py +0 -0
  54. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/__init__.py +0 -0
  55. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/chiral_spg.py +0 -0
  56. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/invert_and_swap.py +0 -0
  57. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/analysis/struct_matcher/reduced_position.py +0 -0
  58. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/__init__.py +0 -0
  59. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/api/cli_rsspolymlp_plot.py +0 -0
  60. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/__init__.py +0 -0
  61. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/convert_dict.py +0 -0
  62. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/common/property.py +0 -0
  63. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/dataset/compress_vasprun.py +0 -0
  64. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/estimate_cost.py +0 -0
  65. {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
  66. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/mlp_dev/pareto_opt_mlp.py +0 -0
  67. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/__init__.py +0 -0
  68. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/load_logfile.py +0 -0
  69. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/rss/random_struct.py +0 -0
  70. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/__init__.py +0 -0
  71. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/lammps_utils.py +0 -0
  72. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/__init__.py +0 -0
  73. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/custom_plt.py +0 -0
  74. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/examples/__init__.py +0 -0
  75. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/matplot_util/make_plot.py +0 -0
  76. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/pymatgen_utils.py +0 -0
  77. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/__init__.py +0 -0
  78. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/api.py +0 -0
  79. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp/utils/vasp_util/gen_incar.py +0 -0
  80. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/dependency_links.txt +0 -0
  81. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/entry_points.txt +0 -0
  82. {rsspolymlp-0.3.3 → rsspolymlp-0.4.0}/src/rsspolymlp.egg-info/requires.txt +0 -0
  83. {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.3
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
- “Efficient global crystal structure prediction using polynomial machine learning potential in the binary Al–Cu alloy system”, [J. Ceram. Soc. Jpn. 131, 762 (2023)](https://www.jstage.jst.go.jp/article/jcersj2/131/10/131_23053/_article/-char/ja/)
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{HayatoWakai202323053,
41
- title="{Efficient global crystal structure prediction using polynomial machine learning potential in the binary Al–Cu alloy system}",
42
- author={Hayato Wakai and Atsuto Seko and Isao Tanaka},
43
- journal={J. Ceram. Soc. Jpn.},
44
- volume={131},
45
- number={10},
46
- pages={762-766},
47
- year={2023},
48
- doi={10.2109/jcersj2.23053}
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/rsspolymlp_devkit.md)
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
- “Efficient global crystal structure prediction using polynomial machine learning potential in the binary Al–Cu alloy system”, [J. Ceram. Soc. Jpn. 131, 762 (2023)](https://www.jstage.jst.go.jp/article/jcersj2/131/10/131_23053/_article/-char/ja/)
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{HayatoWakai202323053,
10
- title="{Efficient global crystal structure prediction using polynomial machine learning potential in the binary Al–Cu alloy system}",
11
- author={Hayato Wakai and Atsuto Seko and Isao Tanaka},
12
- journal={J. Ceram. Soc. Jpn.},
13
- volume={131},
14
- number={10},
15
- pages={762-766},
16
- year={2023},
17
- doi={10.2109/jcersj2.23053}
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/rsspolymlp_devkit.md)
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rsspolymlp"
7
- version = "0.3.3"
7
+ version = "0.4.0"
8
8
  description = "A framework for random structure search using polynomial MLPs"
9
9
  license = "MIT"
10
10
  authors = [
@@ -90,7 +90,7 @@ class EOSFit:
90
90
  else:
91
91
  self.parameters = result[0]
92
92
 
93
- print(f"RMSE of EOS fit (energy): {self.rmse*1000:.6f} meV")
93
+ print(f"RMSE of EOS fit (energy): {self.rmse * 1000:.6f} meV")
94
94
 
95
95
  @property
96
96
  def rmse(self) -> float:
@@ -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.atomic_energy import atomic_energy
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
- vaspobj = Vasprun(vasprun)
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 = [(vaspobj.stress / 10).tolist()[i][i] for i in range(3)]
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
- """Compute formation energies, build convex hull, and evaluate energies above hull."""
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
- self.compute_formation_energies()
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 compute_formation_energies(self):
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
- ref_energies = [
90
+ self.endmember_energies = [
84
91
  np.min(self._composition_data[tuple(r)]["energy"]) for r in pure_sorted
85
92
  ]
86
- ref_energies = np.array(ref_energies)
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["formation_e"])
109
- min_e.append(data["formation_e"][idx])
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
- :param input_paths: List of RSS JSON files or VASP directories.
231
- :param ghost_minima_file: YAML file listing ghost minima to exclude (RSS only).
232
- :param parse_vasp: If True, parse VASP outputs; otherwise parse RSS JSON.
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
- vaspobj = Vasprun(vasprun_path)
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
- composition_tag + ".yaml",
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/{composition_tag}.json", "w") as f:
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/{composition_tag}.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=0.03,
199
- pos_tol=0.03,
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 = abs(e_min - res["energy"])
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
- vaspobj = Vasprun(res["struct_path"])
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, parse_rss_result=False):
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
- p = Path(p)
374
- target = (
375
- base / p if "opt_struct" in p.parts else base / "opt_struct" / p.name
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
- if parse_rss_result:
386
- base = Path(path_name).parents[1]
387
- for r in loaded_dict["rss_results"]:
388
- r["struct_path"] = resolve_path(base, r["struct_path"])
389
- r["dupstr_paths"] = {
390
- resolve_path(base, p) for p in r["dupstr_paths"]
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
- vaspobj = Vasprun(path_name + "/vasprun.xml")
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
  )