packmol-util 0.2.4__py3-none-any.whl
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.
- __init__.py +0 -0
- packmol_util/__init__.py +3 -0
- packmol_util/builder.py +312 -0
- packmol_util/config.py +79 -0
- packmol_util/model.py +621 -0
- packmol_util/packmol.py +491 -0
- packmol_util/packmol_inp.py +252 -0
- packmol_util/param.py +100 -0
- packmol_util-0.2.4.dist-info/METADATA +66 -0
- packmol_util-0.2.4.dist-info/RECORD +12 -0
- packmol_util-0.2.4.dist-info/WHEEL +5 -0
- packmol_util-0.2.4.dist-info/top_level.txt +2 -0
__init__.py
ADDED
|
File without changes
|
packmol_util/__init__.py
ADDED
packmol_util/builder.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
packmol_util/builder.py
|
|
4
|
+
|
|
5
|
+
詳細なファイルのバケツリレーを隠蔽し、
|
|
6
|
+
数行でアモルファス構造を作成するための高レベル統合モジュール。
|
|
7
|
+
|
|
8
|
+
以下の 3 点を外部入力の基準とする:
|
|
9
|
+
1. 化学量論比(整数比)
|
|
10
|
+
2. 密度 [g/cm^3] または 数密度 [1/Å^3] のいずれか一方
|
|
11
|
+
3. セル内の総原子数 total_atom_count
|
|
12
|
+
|
|
13
|
+
【重要】指定した total_atom_count が ratios の合計で割り切れない(組成比を維持できない)場合、
|
|
14
|
+
プログラムが破綻するのを防ぐため、指定された原子数に最も近い「割り切れる総原子数」へ自動補正を行う。
|
|
15
|
+
内部で計算された全体のスケール倍率(multiplier)を外部に公開(プロパティ化)することで、
|
|
16
|
+
利用側のスクリプトがユニット構成単位でのパッキング個数を一元管理できるように設計されている。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Dict, List, Optional, Tuple
|
|
20
|
+
from packmol_util.packmol import Packmol
|
|
21
|
+
from packmol_util.packmol_inp import PackmolInp
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AmorphousBuilder:
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
symbols: List[str],
|
|
29
|
+
ratios: List[int],
|
|
30
|
+
total_atom_count: int, # 希望するベースの総原子数
|
|
31
|
+
minimum_separation: float,
|
|
32
|
+
mass_density: Optional[float] = None,
|
|
33
|
+
number_density: Optional[float] = None,
|
|
34
|
+
packmol_bin: str = "packmol",
|
|
35
|
+
):
|
|
36
|
+
if len(symbols) != len(ratios):
|
|
37
|
+
raise ValueError("symbols と ratios の長さが一致していません。")
|
|
38
|
+
|
|
39
|
+
if total_atom_count <= 0:
|
|
40
|
+
raise ValueError("total_atom_count は正の整数である必要があります。")
|
|
41
|
+
|
|
42
|
+
self.symbols = symbols
|
|
43
|
+
self.ratios = ratios
|
|
44
|
+
self.minimum_separation = minimum_separation
|
|
45
|
+
self.packmol_bin = packmol_bin
|
|
46
|
+
|
|
47
|
+
# --- 【自動補正ロジック】指定できない比率(割り切れない数)のときの自動補正 ---
|
|
48
|
+
ratio_sum = sum(ratios)
|
|
49
|
+
if total_atom_count % ratio_sum != 0:
|
|
50
|
+
# 最も近い整数倍(倍率)を計算
|
|
51
|
+
multiplier = round(total_atom_count / ratio_sum)
|
|
52
|
+
if multiplier < 1:
|
|
53
|
+
multiplier = 1
|
|
54
|
+
adjusted_atom_count = ratio_sum * multiplier
|
|
55
|
+
print(
|
|
56
|
+
f"[Warning] 指定された総原子数 {total_atom_count} は組成比の合計 {ratio_sum} で割り切れません。"
|
|
57
|
+
)
|
|
58
|
+
print(
|
|
59
|
+
f" 組成比を維持するため、総原子数を {adjusted_atom_count} (倍率: {multiplier}) に自動調整しました。"
|
|
60
|
+
)
|
|
61
|
+
self.total_atom_count = adjusted_atom_count
|
|
62
|
+
self._multiplier = multiplier
|
|
63
|
+
else:
|
|
64
|
+
self.total_atom_count = total_atom_count
|
|
65
|
+
self._multiplier = total_atom_count // ratio_sum
|
|
66
|
+
|
|
67
|
+
# 密度の計算とセル一辺の長さ決定
|
|
68
|
+
self.packmol_runner = Packmol()
|
|
69
|
+
if number_density is not None:
|
|
70
|
+
self.box_length = self.packmol_runner.cell_length_from_number_density(
|
|
71
|
+
self.total_atom_count, number_density
|
|
72
|
+
)
|
|
73
|
+
elif mass_density is not None:
|
|
74
|
+
n_density = self.packmol_runner.number_density_from_mass_density(
|
|
75
|
+
symbols, ratios, mass_density
|
|
76
|
+
)
|
|
77
|
+
self.box_length = self.packmol_runner.cell_length_from_number_density(
|
|
78
|
+
self.total_atom_count, n_density
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"mass_density または number_density のどちらかを指定してください。"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.templates: List[dict] = []
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def multiplier(self) -> int:
|
|
89
|
+
"""
|
|
90
|
+
全体の化学量論比に対するセルのスケール倍率を返す。
|
|
91
|
+
利用側のスクリプトは、この倍率をユニットの1式あたり構成比に乗算することで、
|
|
92
|
+
Packmol に引き渡すべき実際の複製個数(number)を算出する。
|
|
93
|
+
"""
|
|
94
|
+
return self._multiplier
|
|
95
|
+
|
|
96
|
+
def add_template(
|
|
97
|
+
self,
|
|
98
|
+
xyz_file: str,
|
|
99
|
+
number: int, # そのテンプレートを Packmol がセル内に何個配置するかを表す実際の複製個数
|
|
100
|
+
packing_radii: Optional[Dict[str, float]] = None,
|
|
101
|
+
fnc_pairs_in_template: Optional[List[Tuple[int, int, int]]] = None,
|
|
102
|
+
fnc_distance_ranges: Optional[Dict[int, Tuple[float, float]]] = None,
|
|
103
|
+
# packmol後に、FNC作成での形状固定の選択
|
|
104
|
+
fnc_output_constraint_types: Optional[List[int]] = None,
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Packmol の 1 つの structure ブロックに対応するテンプレート情報を登録する。
|
|
108
|
+
|
|
109
|
+
引数の説明:
|
|
110
|
+
xyz_file: Packmol に渡す単原子または分子ユニットテンプレートの座標ファイル名 (.xyz)
|
|
111
|
+
number: そのテンプレートを何個複製して配置するかを指定する [int]
|
|
112
|
+
packing_radii: Packmol 配置時に使う元素記号別の排除半径の辞書(任意)。
|
|
113
|
+
分子ユニット内部の結合距離や形状を作る値ではない。
|
|
114
|
+
fnc_pairs_in_template: テンプレート内の局所原子番号で指定した FNC ペア。
|
|
115
|
+
(local_i, local_j, constraint_type) のリストで指定する。
|
|
116
|
+
fnc_distance_ranges: FNC constraint type ごとの距離範囲。
|
|
117
|
+
{type: (rmin, rmax)} で指定する。
|
|
118
|
+
fnc_output_constraint_types: .fnc に実際に出力する constraint type のリスト。
|
|
119
|
+
None の場合は全 type を出力する。
|
|
120
|
+
例: [1] を指定すると type 1 のペアだけを出力する。
|
|
121
|
+
Packmol 用 XYZ テンプレートの形状や配置には影響しない。
|
|
122
|
+
"""
|
|
123
|
+
# builder では、テンプレートをまだ Packmol 入力へ変換しない。
|
|
124
|
+
# ここでは「どの XYZ テンプレートを何個置くか」と、必要なら配置用 packing 半径だけを保存する。
|
|
125
|
+
# 具体的な structure ... end structure への変換は build() 内の PackmolInp が担当する。
|
|
126
|
+
self.templates.append(
|
|
127
|
+
{
|
|
128
|
+
"xyz": xyz_file,
|
|
129
|
+
"number": number,
|
|
130
|
+
"radii": packing_radii,
|
|
131
|
+
"fnc_pairs_in_template": fnc_pairs_in_template,
|
|
132
|
+
"fnc_distance_ranges": fnc_distance_ranges,
|
|
133
|
+
# Packmol は xyz_file のテンプレート形状をそのまま配置する。
|
|
134
|
+
# この指定は Packmol には渡さず、Packmol 後に .fnc を作る段階だけで使う。
|
|
135
|
+
# None: 全 FNC type を出力する。
|
|
136
|
+
# [1]: type 1 の拘束だけを .fnc に出力する。
|
|
137
|
+
"fnc_output_constraint_types": fnc_output_constraint_types,
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def build(self, output_prefix: str, fnc_file: Optional[str] = None):
|
|
142
|
+
# output_prefix は、一連の生成ファイル名の接頭辞として使う。
|
|
143
|
+
# 例: output_prefix="Li3PO4" の場合、Li3PO4_packmol.inp / Li3PO4_packmol.xyz / Li3PO4_packmol.log を作る。
|
|
144
|
+
input_file = f"{output_prefix}_packmol.inp"
|
|
145
|
+
output_xyz = f"{output_prefix}_packmol.xyz"
|
|
146
|
+
output_log = f"{output_prefix}_packmol.log"
|
|
147
|
+
poscar_file = "POSCAR"
|
|
148
|
+
vasp_file = "POSCAR.vasp"
|
|
149
|
+
if fnc_file is None:
|
|
150
|
+
fnc_file = f"{output_prefix}.fnc"
|
|
151
|
+
|
|
152
|
+
# 1. PackmolInp を構築する。
|
|
153
|
+
# AmorphousBuilder が持つ情報は Python 側の抽象表現である。
|
|
154
|
+
# PackmolInp は、それを Packmol の .inp 文法へ落とす役割を持つ。
|
|
155
|
+
inp_builder = PackmolInp(
|
|
156
|
+
minimum_separation_distance=self.minimum_separation,
|
|
157
|
+
output_xyz=output_xyz,
|
|
158
|
+
box_length=self.box_length,
|
|
159
|
+
use_pbc=True,
|
|
160
|
+
)
|
|
161
|
+
for t in self.templates:
|
|
162
|
+
# 1 件の template は Packmol 入力の 1 つの structure ブロックに対応する。
|
|
163
|
+
# 単原子テンプレートでも PO4 のような分子ユニットでも、ここでは同じ登録処理を通す。
|
|
164
|
+
inp_builder.add_structure(
|
|
165
|
+
xyz_file=t["xyz"],
|
|
166
|
+
number=t["number"],
|
|
167
|
+
packing_radii_by_atom_symbol=t["radii"],
|
|
168
|
+
)
|
|
169
|
+
# 登録した template 群を Packmol 入力ファイルとして書き出す。
|
|
170
|
+
inp_builder.write(input_file)
|
|
171
|
+
|
|
172
|
+
# 2. Packmol を実行し、生成された XYZ を POSCAR に変換する。
|
|
173
|
+
self.packmol_runner.run_packmol(
|
|
174
|
+
input_file=input_file,
|
|
175
|
+
output_log=output_log,
|
|
176
|
+
packmol_bin=self.packmol_bin,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Packmol 出力は Cartesian 座標なので、VASP POSCAR 用に Direct 座標へ変換する。
|
|
180
|
+
xyz_to_poscar_index = self.packmol_runner.xyz_to_poscar(
|
|
181
|
+
xyz_file=output_xyz,
|
|
182
|
+
poscar_file=poscar_file,
|
|
183
|
+
packmol_inp=input_file,
|
|
184
|
+
comment=f"{output_prefix} amorphous initial model generated by Packmol (Total Atoms: {self.total_atom_count})",
|
|
185
|
+
elements=self.symbols,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self._write_fnc_from_templates(
|
|
189
|
+
output_xyz=output_xyz,
|
|
190
|
+
xyz_to_poscar_index=xyz_to_poscar_index,
|
|
191
|
+
fnc_file=fnc_file,
|
|
192
|
+
output_prefix=output_prefix,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# 外部ツール連携用に vasp.xyz(あるいは POSCAR.vasp)も同様に出力する。
|
|
196
|
+
self.packmol_runner.xyz_to_poscar(
|
|
197
|
+
xyz_file=output_xyz,
|
|
198
|
+
poscar_file=vasp_file,
|
|
199
|
+
packmol_inp=input_file,
|
|
200
|
+
comment=f"{output_prefix} amorphous initial model vasp format",
|
|
201
|
+
elements=self.symbols,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _write_fnc_from_templates(
|
|
205
|
+
self,
|
|
206
|
+
output_xyz: str,
|
|
207
|
+
xyz_to_poscar_index: Dict[int, int],
|
|
208
|
+
fnc_file: str,
|
|
209
|
+
output_prefix: str,
|
|
210
|
+
) -> None:
|
|
211
|
+
"""
|
|
212
|
+
登録済みテンプレートの FNC 局所ペアを、最終 POSCAR/cfg 番号へ変換して *.fnc を書く。
|
|
213
|
+
|
|
214
|
+
Packmol 出力 XYZ ではテンプレート単位の原子順が残る。一方、POSCAR 変換では
|
|
215
|
+
元素ごとに座標を並べ替えるため、PO4 の P と O の番号は離れる。
|
|
216
|
+
このメソッドは、Packmol XYZ 番号を中間番号として使い、
|
|
217
|
+
テンプレート内局所番号から最終 POSCAR/cfg 番号の FNC ペアを作る。
|
|
218
|
+
"""
|
|
219
|
+
all_fnc_pairs: List[Tuple[int, int, int]] = []
|
|
220
|
+
merged_distance_ranges: Dict[int, Tuple[float, float]] = {}
|
|
221
|
+
|
|
222
|
+
xyz_start_index = 1
|
|
223
|
+
for template in self.templates:
|
|
224
|
+
xyz_file = str(template["xyz"])
|
|
225
|
+
number = int(template["number"])
|
|
226
|
+
fnc_pairs_in_template = template.get("fnc_pairs_in_template")
|
|
227
|
+
fnc_distance_ranges = template.get("fnc_distance_ranges")
|
|
228
|
+
# packmol後に形状固定でのFNCの選択有無
|
|
229
|
+
fnc_output_constraint_types = template.get("fnc_output_constraint_types")
|
|
230
|
+
|
|
231
|
+
atom_count_in_template = self.packmol_runner.count_atoms_in_xyz(
|
|
232
|
+
xyz_file=xyz_file,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if fnc_pairs_in_template is not None:
|
|
236
|
+
if fnc_distance_ranges is None:
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f"{xyz_file} に fnc_pairs_in_template が指定されているが、"
|
|
239
|
+
"fnc_distance_ranges が指定されていない。"
|
|
240
|
+
)
|
|
241
|
+
########################################
|
|
242
|
+
# Packmol 用テンプレート構造は変更しない。
|
|
243
|
+
# ここでは Packmol 後の最終構造から .fnc を作る段階だけで、
|
|
244
|
+
# RMC に渡す constraint type を選別する。
|
|
245
|
+
if fnc_output_constraint_types is None:
|
|
246
|
+
active_constraint_types = list(fnc_distance_ranges.keys())
|
|
247
|
+
else:
|
|
248
|
+
active_constraint_types = list(fnc_output_constraint_types)
|
|
249
|
+
|
|
250
|
+
active_constraint_type_set = set(active_constraint_types)
|
|
251
|
+
|
|
252
|
+
for type_index in active_constraint_types:
|
|
253
|
+
if type_index not in fnc_distance_ranges:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"{xyz_file} の fnc_output_constraint_types に type={type_index} "
|
|
256
|
+
"が指定されているが、fnc_distance_ranges に存在しない。"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
active_fnc_pairs_in_template = [
|
|
260
|
+
pair
|
|
261
|
+
for pair in fnc_pairs_in_template
|
|
262
|
+
if pair[2] in active_constraint_type_set
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
active_fnc_distance_ranges = {
|
|
266
|
+
type_index: fnc_distance_ranges[type_index]
|
|
267
|
+
for type_index in active_constraint_types
|
|
268
|
+
}
|
|
269
|
+
############################
|
|
270
|
+
|
|
271
|
+
for type_index, distance_range in active_fnc_distance_ranges.items():
|
|
272
|
+
if type_index in merged_distance_ranges:
|
|
273
|
+
if merged_distance_ranges[type_index] != distance_range:
|
|
274
|
+
raise ValueError(
|
|
275
|
+
"同じ FNC constraint type に異なる距離範囲が指定されている。"
|
|
276
|
+
f" type={type_index}"
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
merged_distance_ranges[type_index] = distance_range
|
|
280
|
+
|
|
281
|
+
copy_index = 0
|
|
282
|
+
while copy_index < number:
|
|
283
|
+
molecule_xyz_start = (
|
|
284
|
+
xyz_start_index + copy_index * atom_count_in_template
|
|
285
|
+
)
|
|
286
|
+
for (
|
|
287
|
+
local_i,
|
|
288
|
+
local_j,
|
|
289
|
+
constraint_type,
|
|
290
|
+
) in active_fnc_pairs_in_template:
|
|
291
|
+
xyz_i = molecule_xyz_start + local_i - 1
|
|
292
|
+
xyz_j = molecule_xyz_start + local_j - 1
|
|
293
|
+
poscar_i = xyz_to_poscar_index[xyz_i]
|
|
294
|
+
poscar_j = xyz_to_poscar_index[xyz_j]
|
|
295
|
+
all_fnc_pairs.append((poscar_i, poscar_j, constraint_type))
|
|
296
|
+
copy_index = copy_index + 1
|
|
297
|
+
|
|
298
|
+
xyz_start_index = xyz_start_index + number * atom_count_in_template
|
|
299
|
+
|
|
300
|
+
if len(all_fnc_pairs) == 0:
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
self.packmol_runner.write_fnc_file(
|
|
304
|
+
fnc_file=fnc_file,
|
|
305
|
+
total_atom_count=self.total_atom_count,
|
|
306
|
+
fnc_pairs=all_fnc_pairs,
|
|
307
|
+
distance_ranges_by_type=merged_distance_ranges,
|
|
308
|
+
title=f"{output_prefix} FNC generated from Packmol template mapping",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
#
|
packmol_util/config.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""packmol_util の設定保持クラス。"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Sequence, Union
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from x_logger import XLogger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Config:
|
|
13
|
+
"""packmol_util 用の設定値を保持する。"""
|
|
14
|
+
|
|
15
|
+
N_tot: int = 2000
|
|
16
|
+
mass_density_g_cm3: float = 7.3336
|
|
17
|
+
number_density_A3: float = 0.09119
|
|
18
|
+
element_info: Union[str, Sequence[str]] = ["Fe", "Si", "B"]
|
|
19
|
+
ratios: Sequence[int] = [82, 4, 14]
|
|
20
|
+
|
|
21
|
+
packmol_bin: str = "packmol"
|
|
22
|
+
minimum_separation: float = 2.0
|
|
23
|
+
output_prefix: str = "amorphous"
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
yaml_path: Optional[str] = None,
|
|
28
|
+
logger: Optional[XLogger] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
if logger is None:
|
|
31
|
+
logger = XLogger(logger_name=__name__)
|
|
32
|
+
self._logger = logger
|
|
33
|
+
|
|
34
|
+
if yaml_path:
|
|
35
|
+
self._load_yaml(yaml_path)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def load_yaml(cls, yaml_path: Optional[str] = None) -> None:
|
|
39
|
+
"""YAML の内容でクラス変数を上書きする。"""
|
|
40
|
+
if yaml_path is None:
|
|
41
|
+
yaml_path = os.environ.get("PACKMOL_UTIL_CONFIG_YAML")
|
|
42
|
+
if yaml_path is None:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
with open(yaml_path, "r") as f:
|
|
46
|
+
data = yaml.safe_load(f)
|
|
47
|
+
|
|
48
|
+
if data is None:
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
for key, value in data.items():
|
|
52
|
+
if hasattr(cls, key):
|
|
53
|
+
setattr(cls, key, value)
|
|
54
|
+
else:
|
|
55
|
+
print("NO SUCH VAR:", key)
|
|
56
|
+
|
|
57
|
+
def _load_yaml(self, yaml_path: Optional[str] = None) -> None:
|
|
58
|
+
"""YAML の内容でインスタンス属性を上書きする。"""
|
|
59
|
+
self._logger.info(f"load_yaml: {yaml_path}")
|
|
60
|
+
|
|
61
|
+
if yaml_path is None:
|
|
62
|
+
yaml_path = os.environ.get("PACKMOL_UTIL_CONFIG_YAML")
|
|
63
|
+
if yaml_path is None:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
yaml_path = os.path.abspath(yaml_path)
|
|
67
|
+
self._logger.info(f"abspath: {yaml_path}")
|
|
68
|
+
|
|
69
|
+
with open(yaml_path, "r") as f:
|
|
70
|
+
data = yaml.safe_load(f)
|
|
71
|
+
|
|
72
|
+
if data is None:
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
for key, value in data.items():
|
|
76
|
+
if hasattr(self, key):
|
|
77
|
+
setattr(self, key, value)
|
|
78
|
+
else:
|
|
79
|
+
print("NO SUCH VAR:", key)
|