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
packmol_util/model.py
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
材料非依存の構造テンプレートと Packmol 用レシピを準備する共通モジュールである。
|
|
4
|
+
|
|
5
|
+
このファイルには、Li3PO4 や FeSiB のような物質固有の数値を置かない。
|
|
6
|
+
物質固有の原子数、密度、分子内距離、packing 半径は make_*.py 側で与える。
|
|
7
|
+
|
|
8
|
+
ここで行うことは、次の二つである。
|
|
9
|
+
|
|
10
|
+
1. Packmol に渡す単原子テンプレートまたは分子ユニットテンプレートの XYZ を作る。
|
|
11
|
+
2. make_*.py 側で決めたテンプレートと個数を、Packmol 入力用のレシピにまとめる。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Dict, List, Optional, Sequence, Tuple
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class StructureSpec:
|
|
21
|
+
"""
|
|
22
|
+
Packmol の 1 つの structure ブロックに対応する情報である。
|
|
23
|
+
|
|
24
|
+
xyz_file は、Packmol に渡す 1 個の単原子テンプレートまたは 1 個の分子ユニット
|
|
25
|
+
テンプレートの座標ファイル名である。
|
|
26
|
+
|
|
27
|
+
number は、そのテンプレートを Packmol がセル内に何個配置するかを表す。
|
|
28
|
+
例えば Li.xyz の number が 192 なら、Li 単原子テンプレートを 192 個配置する。
|
|
29
|
+
PO4.xyz の number が 64 なら、PO4 ユニットを 64 個配置する。
|
|
30
|
+
|
|
31
|
+
packing_radii_by_atom_symbol は、Packmol の atoms ... radius ... end atoms 指定へ
|
|
32
|
+
変換するための情報である。キーは元素記号であり、Packmol の atom index ではない。
|
|
33
|
+
Packmol 入力ファイルに書くときに、packmol_inp.py が XYZ 内の原子順を読んで、
|
|
34
|
+
元素記号から atom index へ変換する。
|
|
35
|
+
|
|
36
|
+
この packing 半径は分子ユニット内部の結合距離ではない。
|
|
37
|
+
分子ユニット内部の距離は、write_tetrahedral_unit_xyz() などのテンプレート生成関数で
|
|
38
|
+
決める。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# Packmol に渡す 1 個のテンプレート XYZ ファイルである。
|
|
42
|
+
# 単原子系なら Fe.xyz や Li.xyz、分子ユニット系なら PO4.xyz などを指す。
|
|
43
|
+
xyz_file: str
|
|
44
|
+
|
|
45
|
+
# 上のテンプレートをセル内に何個配置するかを表す。
|
|
46
|
+
# PO4.xyz の number=64 は、PO4 ユニットを 64 個配置するという意味である。
|
|
47
|
+
number: int
|
|
48
|
+
|
|
49
|
+
# Packmol の atoms ... radius ... end atoms へ変換するための指定である。
|
|
50
|
+
# make_*.py 側では元素記号で書き、packmol_inp.py 側で XYZ 内 atom index へ変換する。
|
|
51
|
+
# これは分子内距離ではなく、配置時の packing 用排除半径である。
|
|
52
|
+
packing_radii_by_atom_symbol: Optional[Dict[str, float]] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class TetrahedralUnitFncSpec:
|
|
57
|
+
"""
|
|
58
|
+
正四面体ユニットの FNC 指定と Packmol 用代表距離をまとめた仕様である。
|
|
59
|
+
|
|
60
|
+
FNC の rmin/rmax を一次情報とし、その中央値から Packmol に渡す
|
|
61
|
+
テンプレート構造用の代表距離を派生させる。
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
center_vertex_distance: float
|
|
65
|
+
vertex_vertex_distance: float
|
|
66
|
+
fnc_pairs_in_template: List[Tuple[int, int, int]]
|
|
67
|
+
fnc_distance_ranges: Dict[int, Tuple[float, float]]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class MaterialRecipe:
|
|
72
|
+
"""
|
|
73
|
+
make_*.py 側で定義した構造作成条件を Packmol 入力生成へ渡すためのレシピである。
|
|
74
|
+
|
|
75
|
+
total_atom_count は、セル内に最終的に入る総原子数である。
|
|
76
|
+
Li3PO4 で Li 原子 192 個と PO4 ユニット 64 個を置く場合、総原子数は
|
|
77
|
+
192 + 64 * 5 = 512 である。
|
|
78
|
+
|
|
79
|
+
symbols と ratios は、最終的な POSCAR 変換や密度計算で使う元素順序と組成比である。
|
|
80
|
+
structures は、Packmol に渡す structure ブロックの列である。
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# 最終的に POSCAR に入る実原子数である。
|
|
84
|
+
# structure 数ではなく、テンプレート内原子数も掛けた値である。
|
|
85
|
+
total_atom_count: int
|
|
86
|
+
|
|
87
|
+
# POSCAR に出力する元素順序である。
|
|
88
|
+
symbols: List[str]
|
|
89
|
+
|
|
90
|
+
# 元素順序に対応する組成比である。
|
|
91
|
+
ratios: List[int]
|
|
92
|
+
|
|
93
|
+
# Packmol に渡す structure ブロックの列である。
|
|
94
|
+
structures: List[StructureSpec]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def write_xyz_template(
|
|
98
|
+
filename: str,
|
|
99
|
+
atoms: Sequence[Tuple[str, float, float, float]],
|
|
100
|
+
comment: str = "template",
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
任意のテンプレート構造を XYZ として書き出す。
|
|
104
|
+
|
|
105
|
+
atoms には、1 個の単原子テンプレートまたは 1 個の分子ユニットテンプレートの
|
|
106
|
+
原子座標を渡す。ここで書かれた原子順が、Packmol の atom index の順番になる。
|
|
107
|
+
"""
|
|
108
|
+
# XYZ 形式では 1 行目にテンプレート内原子数を書く。
|
|
109
|
+
# Packmol はこの XYZ を 1 個の structure テンプレートとして読む。
|
|
110
|
+
with open(filename, "w") as f:
|
|
111
|
+
f.write(f"{len(atoms)}\n")
|
|
112
|
+
|
|
113
|
+
# 2 行目はコメント行である。Packmol の配置には使わない。
|
|
114
|
+
f.write(f"{comment}\n")
|
|
115
|
+
|
|
116
|
+
# 3 行目以降が、テンプレート内部の原子順序と座標である。
|
|
117
|
+
# この順序が Packmol の atoms 1, atoms 2, ... の番号に対応する。
|
|
118
|
+
for symbol, x_value, y_value, z_value in atoms:
|
|
119
|
+
f.write(f"{symbol} {x_value:.10f} {y_value:.10f} {z_value:.10f}\n")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def write_single_atom_xyz(
|
|
123
|
+
filename: str,
|
|
124
|
+
element: str,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Packmol に単原子を配置させるための 1 原子 XYZ テンプレートを書き出す。
|
|
128
|
+
|
|
129
|
+
これは FeSiB の Fe、Si、B のように、分子ユニットを作らず、元素ごとの単原子を
|
|
130
|
+
ランダム配置したい場合に使う。
|
|
131
|
+
"""
|
|
132
|
+
# 単原子テンプレートでは、テンプレート内に原子を 1 個だけ置く。
|
|
133
|
+
# Packmol はこの 1 原子テンプレートを number 個だけ複製して配置する。
|
|
134
|
+
write_xyz_template(
|
|
135
|
+
filename=filename,
|
|
136
|
+
atoms=[(element, 0.0, 0.0, 0.0)],
|
|
137
|
+
comment=f"{element} atom template",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _validate_regular_tetrahedron_distances(
|
|
142
|
+
center_vertex_distance: float,
|
|
143
|
+
vertex_vertex_distance: float,
|
|
144
|
+
distance_tolerance: float,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""
|
|
147
|
+
正四面体ユニットの中心-頂点距離と頂点-頂点距離の整合性を確認する。
|
|
148
|
+
|
|
149
|
+
正四面体では、中心原子から各頂点原子までの距離を指定すると、頂点原子同士の
|
|
150
|
+
距離は幾何学的に決まる。したがって、P-O と O-O を同時に入力として受ける場合、
|
|
151
|
+
両者が正四面体として矛盾していないか確認する必要がある。
|
|
152
|
+
"""
|
|
153
|
+
if center_vertex_distance <= 0.0:
|
|
154
|
+
raise ValueError("center_vertex_distance は正の値で指定する。")
|
|
155
|
+
|
|
156
|
+
if vertex_vertex_distance <= 0.0:
|
|
157
|
+
raise ValueError("vertex_vertex_distance は正の値で指定する。")
|
|
158
|
+
|
|
159
|
+
if distance_tolerance < 0.0:
|
|
160
|
+
raise ValueError("distance_tolerance は 0 以上の値で指定する。")
|
|
161
|
+
|
|
162
|
+
# 正四面体では、中心-頂点距離を決めると頂点-頂点距離は一意に決まる。
|
|
163
|
+
# PO4 なら P-O 距離から O-O 距離が決まる。
|
|
164
|
+
expected_vertex_vertex_distance: float = center_vertex_distance * math.sqrt(8.0 / 3.0)
|
|
165
|
+
|
|
166
|
+
# make_*.py 側で明示された O-O 距離が、正四面体幾何から外れていないかを見る。
|
|
167
|
+
distance_error: float = abs(expected_vertex_vertex_distance - vertex_vertex_distance)
|
|
168
|
+
|
|
169
|
+
if distance_error > distance_tolerance:
|
|
170
|
+
message = (
|
|
171
|
+
"正四面体では center_vertex_distance と vertex_vertex_distance を "
|
|
172
|
+
"独立には指定できない。"
|
|
173
|
+
f" center_vertex_distance={center_vertex_distance:.10f},"
|
|
174
|
+
f" vertex_vertex_distance={vertex_vertex_distance:.10f},"
|
|
175
|
+
f" expected_vertex_vertex_distance={expected_vertex_vertex_distance:.10f},"
|
|
176
|
+
f" distance_error={distance_error:.10f},"
|
|
177
|
+
f" distance_tolerance={distance_tolerance:.10f}"
|
|
178
|
+
)
|
|
179
|
+
raise ValueError(message)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def write_tetrahedral_unit_xyz(
|
|
183
|
+
filename: str,
|
|
184
|
+
center_element: str,
|
|
185
|
+
vertex_element: str,
|
|
186
|
+
center_vertex_distance: float,
|
|
187
|
+
vertex_vertex_distance: float,
|
|
188
|
+
distance_tolerance: float = 0.05,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""
|
|
191
|
+
中心原子 1 個と頂点原子 4 個からなる正四面体ユニットを書き出す。
|
|
192
|
+
|
|
193
|
+
PO4 の場合、center_element は P、vertex_element は O である。
|
|
194
|
+
center_vertex_distance は P-O 距離である。
|
|
195
|
+
vertex_vertex_distance は O-O 距離である。
|
|
196
|
+
|
|
197
|
+
この関数で作る XYZ が、Packmol に渡す「1 個の PO4 ユニット」の形になる。
|
|
198
|
+
Packmol はこの XYZ を読み、PO4 ユニットの内部形状を保ったまま、指定個数を
|
|
199
|
+
セル内に配置する。
|
|
200
|
+
|
|
201
|
+
packing 半径や最小分離距離はここでは扱わない。それらは、すでに作った
|
|
202
|
+
ユニット同士、またはユニットと単原子が配置時に近づきすぎないようにするための
|
|
203
|
+
Packmol 入力側の条件である。
|
|
204
|
+
"""
|
|
205
|
+
_validate_regular_tetrahedron_distances(
|
|
206
|
+
center_vertex_distance=center_vertex_distance,
|
|
207
|
+
vertex_vertex_distance=vertex_vertex_distance,
|
|
208
|
+
distance_tolerance=distance_tolerance,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# (±a, ±a, ±a) 型の 4 点を選ぶと、中心原子を原点に置いた正四面体になる。
|
|
212
|
+
# 中心から各頂点までの距離は sqrt(3) * a なので、a を下のように決める。
|
|
213
|
+
scale: float = center_vertex_distance / math.sqrt(3.0)
|
|
214
|
+
|
|
215
|
+
# 1 番目の原子が中心原子、2-5 番目の原子が頂点原子である。
|
|
216
|
+
# この順序は Packmol の atom index にも反映される。
|
|
217
|
+
atoms: List[Tuple[str, float, float, float]] = [
|
|
218
|
+
(center_element, 0.0, 0.0, 0.0),
|
|
219
|
+
(vertex_element, scale, scale, scale),
|
|
220
|
+
(vertex_element, scale, -scale, -scale),
|
|
221
|
+
(vertex_element, -scale, scale, -scale),
|
|
222
|
+
(vertex_element, -scale, -scale, scale),
|
|
223
|
+
]
|
|
224
|
+
write_xyz_template(
|
|
225
|
+
filename=filename,
|
|
226
|
+
atoms=atoms,
|
|
227
|
+
comment=f"{center_element}{vertex_element}4 tetrahedral unit template",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def make_material_recipe(
|
|
232
|
+
symbols: Sequence[str],
|
|
233
|
+
ratios: Sequence[int],
|
|
234
|
+
structures: Sequence[StructureSpec],
|
|
235
|
+
) -> MaterialRecipe:
|
|
236
|
+
"""
|
|
237
|
+
make_*.py 側で作った structure 指定を Packmol 用レシピにまとめる。
|
|
238
|
+
|
|
239
|
+
この関数は分子構造を作らない。Packmol 入力ファイルも書かない。
|
|
240
|
+
すでに作られた XYZ テンプレートと、それぞれを何個配置するかという情報を、
|
|
241
|
+
MaterialRecipe として一つにまとめる。
|
|
242
|
+
|
|
243
|
+
Li3PO4 では、Li.xyz を何個置くか、PO4.xyz を何個置くかを structures に渡す。
|
|
244
|
+
FeSiB では、Fe.xyz、Si.xyz、B.xyz をそれぞれ何個置くかを structures に渡す。
|
|
245
|
+
"""
|
|
246
|
+
if len(symbols) != len(ratios):
|
|
247
|
+
raise ValueError("symbols と ratios の長さが一致していません。")
|
|
248
|
+
|
|
249
|
+
# Sequence のまま保持せず、後段で扱いやすい list に固定する。
|
|
250
|
+
# 以後の処理では何度も走査するため、Sequence を list に固定する。
|
|
251
|
+
symbol_list: List[str] = list(symbols)
|
|
252
|
+
ratio_list: List[int] = list(ratios)
|
|
253
|
+
structure_list: List[StructureSpec] = list(structures)
|
|
254
|
+
|
|
255
|
+
# total_atom_count は、Packmol の structure 個数の合計ではない。
|
|
256
|
+
# PO4 のように 1 テンプレートが 5 原子を含む場合があるため、XYZ 先頭行から
|
|
257
|
+
# テンプレート内原子数を読み、number と掛け合わせて総原子数を作る。
|
|
258
|
+
total_atom_count: int = 0
|
|
259
|
+
for structure in structure_list:
|
|
260
|
+
atom_count_in_template: int = _count_atoms_in_xyz_file(
|
|
261
|
+
xyz_file=structure.xyz_file,
|
|
262
|
+
)
|
|
263
|
+
total_atom_count = total_atom_count + structure.number * atom_count_in_template
|
|
264
|
+
|
|
265
|
+
return MaterialRecipe(
|
|
266
|
+
total_atom_count=total_atom_count,
|
|
267
|
+
symbols=symbol_list,
|
|
268
|
+
ratios=ratio_list,
|
|
269
|
+
structures=structure_list,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _normalize_atom_pair(
|
|
273
|
+
atom_symbol_a: str,
|
|
274
|
+
atom_symbol_b: str,
|
|
275
|
+
) -> Tuple[str, str]:
|
|
276
|
+
"""
|
|
277
|
+
元素ペアを順序非依存のキーに正規化する。
|
|
278
|
+
|
|
279
|
+
Li-P と P-Li は同じ pair distance を表すため、辞書キーを常に同じ順序へそろえる。
|
|
280
|
+
ここでは文字列順を使うだけであり、元素の物理的な大小関係を意味しない。
|
|
281
|
+
"""
|
|
282
|
+
# pair distance は元素ペアの順序に意味を持たない。
|
|
283
|
+
# そのため、入力が ("Li", "O") でも ("O", "Li") でも同じキーへ落とす。
|
|
284
|
+
if atom_symbol_a <= atom_symbol_b:
|
|
285
|
+
return atom_symbol_a, atom_symbol_b
|
|
286
|
+
return atom_symbol_b, atom_symbol_a
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def solve_packing_radii_from_pair_distances(
|
|
290
|
+
symbols: Sequence[str],
|
|
291
|
+
minimum_distances_by_pair: Dict[Tuple[str, str], float],
|
|
292
|
+
pair_distance_tolerance: float = 0.0,
|
|
293
|
+
) -> Dict[str, float]:
|
|
294
|
+
"""
|
|
295
|
+
希望する元素ペア距離を Packmol の原子半径モデルへ近似変換する。
|
|
296
|
+
|
|
297
|
+
Packmol は Li-O=1.55, P-O=1.30 のような元素ペア距離表を直接受け取らない。
|
|
298
|
+
Packmol が受け取れるのは atom index ごとの radius であり、実効距離は
|
|
299
|
+
radius_i + radius_j として扱われる。
|
|
300
|
+
|
|
301
|
+
この関数は、make_*.py 側で書いた元素ペア距離表を、Packmol に渡せる
|
|
302
|
+
元素ごとの packing radius に変換するための補助関数である。
|
|
303
|
+
|
|
304
|
+
ただし、任意の元素ペア距離表が半径和で完全に表せるわけではない。
|
|
305
|
+
例えば Li-Li, Li-P, Li-O, P-P, P-O, O-O をすべて独立に与えると、
|
|
306
|
+
半径和モデルでは矛盾する場合がある。
|
|
307
|
+
|
|
308
|
+
pair_distance_tolerance は、その矛盾をどの程度まで短い距離側へ緩めて許すかを表す。
|
|
309
|
+
この値は Packmol の tolerance ではない。元素ペア距離表を packing radius に
|
|
310
|
+
近似変換するための内部的な許容幅である。
|
|
311
|
+
|
|
312
|
+
戻り値は {"Li": r_Li, "P": r_P, "O": r_O} のような辞書である。
|
|
313
|
+
この辞書は StructureSpec.packing_radii_by_atom_symbol に渡し、
|
|
314
|
+
packmol_inp.py 側で atom index 指定へ変換される。
|
|
315
|
+
|
|
316
|
+
注意:この変換で得られる半径は、分子ユニット内部の距離を決めるものではない。
|
|
317
|
+
例えば PO4 の P-O / O-O 距離は write_tetrahedral_unit_xyz() が作る XYZ 座標で決まる。
|
|
318
|
+
この関数が扱うのは、Packmol が複数ユニットを配置するときに使う半径和モデルである。
|
|
319
|
+
"""
|
|
320
|
+
symbol_list: List[str] = list(symbols)
|
|
321
|
+
if len(symbol_list) == 0:
|
|
322
|
+
raise ValueError("symbols は 1 つ以上必要である。")
|
|
323
|
+
|
|
324
|
+
if pair_distance_tolerance < 0.0:
|
|
325
|
+
raise ValueError("pair_distance_tolerance は 0 以上である必要がある。")
|
|
326
|
+
|
|
327
|
+
# 入力された pair distance 辞書を、順序非依存の正規化キーで作り直す。
|
|
328
|
+
# これにより ("Li", "O") と ("O", "Li") を同じ指定として扱う。
|
|
329
|
+
normalized_minimum_distances_by_pair: Dict[Tuple[str, str], float] = {}
|
|
330
|
+
for raw_pair_key, distance in minimum_distances_by_pair.items():
|
|
331
|
+
if len(raw_pair_key) != 2:
|
|
332
|
+
raise ValueError(f"pair key は 2 要素タプルで指定する: {raw_pair_key}")
|
|
333
|
+
|
|
334
|
+
atom_symbol_a, atom_symbol_b = raw_pair_key
|
|
335
|
+
normalized_pair_key = _normalize_atom_pair(atom_symbol_a, atom_symbol_b)
|
|
336
|
+
|
|
337
|
+
if distance <= 0.0:
|
|
338
|
+
raise ValueError(
|
|
339
|
+
f"元素ペア距離 {normalized_pair_key} は正の値である必要がある。"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
normalized_minimum_distances_by_pair[normalized_pair_key] = distance
|
|
343
|
+
|
|
344
|
+
# 半径和モデルで確認すべき全ペアを作る。
|
|
345
|
+
# symbols=[Li, P, O] なら Li-Li, Li-P, Li-O, P-P, P-O, O-O を確認する。
|
|
346
|
+
pair_keys: List[Tuple[str, str]] = []
|
|
347
|
+
for i, atom_symbol_a in enumerate(symbol_list):
|
|
348
|
+
for atom_symbol_b in symbol_list[i:]:
|
|
349
|
+
pair_key = _normalize_atom_pair(atom_symbol_a, atom_symbol_b)
|
|
350
|
+
if pair_key not in normalized_minimum_distances_by_pair:
|
|
351
|
+
raise ValueError(f"元素ペア距離 {pair_key} が指定されていない。")
|
|
352
|
+
pair_keys.append(pair_key)
|
|
353
|
+
|
|
354
|
+
# best_radii は、半径和で全ペア条件を満たす候補のうち、
|
|
355
|
+
# 元の pair distance からの総短縮量が最も小さいものを保持する。
|
|
356
|
+
best_radii: Optional[Dict[str, float]] = None
|
|
357
|
+
best_score: Optional[float] = None
|
|
358
|
+
|
|
359
|
+
# 自己ペア距離 Li-Li, P-P, O-O を少しずつ短くしながら候補を探索する。
|
|
360
|
+
# 自己ペア距離を決めると r_Li, r_P, r_O が決まり、そこから Li-P などの異種ペア距離も決まる。
|
|
361
|
+
# 完全な連続最適化ではなく、サンプル用の離散探索である。
|
|
362
|
+
step_count: int = 40
|
|
363
|
+
|
|
364
|
+
# 各元素について、自己ペア距離をどこまで緩めるかの候補リストを作る。
|
|
365
|
+
# 自己ペア距離 d_ii が決まれば、その元素の packing radius は d_ii / 2 になる。
|
|
366
|
+
diagonal_ranges: List[List[float]] = []
|
|
367
|
+
for symbol in symbol_list:
|
|
368
|
+
diagonal_key = _normalize_atom_pair(symbol, symbol)
|
|
369
|
+
requested_distance = normalized_minimum_distances_by_pair[diagonal_key]
|
|
370
|
+
|
|
371
|
+
values: List[float] = []
|
|
372
|
+
for step_index in range(step_count + 1):
|
|
373
|
+
reduction = pair_distance_tolerance * float(step_index) / float(step_count)
|
|
374
|
+
diagonal_distance = requested_distance - reduction
|
|
375
|
+
if diagonal_distance > 0.0:
|
|
376
|
+
values.append(diagonal_distance)
|
|
377
|
+
|
|
378
|
+
if len(values) == 0:
|
|
379
|
+
raise ValueError(
|
|
380
|
+
f"pair_distance_tolerance が大きすぎて自己ペア距離が非正になる: {diagonal_key}"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
diagonal_ranges.append(values)
|
|
384
|
+
|
|
385
|
+
def _search(
|
|
386
|
+
depth: int,
|
|
387
|
+
current_diagonal_distances: Dict[str, float],
|
|
388
|
+
) -> None:
|
|
389
|
+
nonlocal best_radii
|
|
390
|
+
nonlocal best_score
|
|
391
|
+
|
|
392
|
+
if depth == len(symbol_list):
|
|
393
|
+
# すべての自己ペア距離候補が決まったので、元素ごとの半径へ変換する。
|
|
394
|
+
radii: Dict[str, float] = {}
|
|
395
|
+
for symbol in symbol_list:
|
|
396
|
+
radii[symbol] = current_diagonal_distances[symbol] / 2.0
|
|
397
|
+
|
|
398
|
+
total_reduction: float = 0.0
|
|
399
|
+
for pair_key in pair_keys:
|
|
400
|
+
atom_symbol_a, atom_symbol_b = pair_key
|
|
401
|
+
requested_distance = normalized_minimum_distances_by_pair[pair_key]
|
|
402
|
+
effective_distance = radii[atom_symbol_a] + radii[atom_symbol_b]
|
|
403
|
+
|
|
404
|
+
# 半径和が要求距離より大きい場合は、Packmol の排除距離が強すぎる。
|
|
405
|
+
# ここでは「指定値以下に緩める」方針なので、この候補は棄却する。
|
|
406
|
+
if effective_distance > requested_distance:
|
|
407
|
+
return
|
|
408
|
+
|
|
409
|
+
# 半径和が要求距離より短い場合、その短縮量を評価する。
|
|
410
|
+
# 短縮量が pair_distance_tolerance を超える候補は棄却する。
|
|
411
|
+
reduction = requested_distance - effective_distance
|
|
412
|
+
if reduction > pair_distance_tolerance:
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
total_reduction += reduction
|
|
416
|
+
|
|
417
|
+
if best_score is None or total_reduction < best_score:
|
|
418
|
+
best_score = total_reduction
|
|
419
|
+
best_radii = dict(radii)
|
|
420
|
+
return
|
|
421
|
+
|
|
422
|
+
# 次の元素について、許された自己ペア距離候補を順に試す。
|
|
423
|
+
symbol = symbol_list[depth]
|
|
424
|
+
for diagonal_distance in diagonal_ranges[depth]:
|
|
425
|
+
current_diagonal_distances[symbol] = diagonal_distance
|
|
426
|
+
_search(depth + 1, current_diagonal_distances)
|
|
427
|
+
|
|
428
|
+
_search(depth=0, current_diagonal_distances={})
|
|
429
|
+
|
|
430
|
+
if best_radii is None:
|
|
431
|
+
raise ValueError(
|
|
432
|
+
"指定された pair distance は、許容した緩和幅の中では両立しない。"
|
|
433
|
+
f" pair_distance_tolerance={pair_distance_tolerance:.6f}"
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return best_radii
|
|
437
|
+
|
|
438
|
+
def _count_atoms_in_xyz_file(
|
|
439
|
+
xyz_file: str,
|
|
440
|
+
) -> int:
|
|
441
|
+
"""
|
|
442
|
+
XYZ ファイル先頭行から、1 テンプレート中の原子数を読む。
|
|
443
|
+
|
|
444
|
+
単原子テンプレートなら 1、PO4 テンプレートなら 5 を返す。
|
|
445
|
+
MaterialRecipe.total_atom_count を、structure の個数ではなく実際の総原子数にするために使う。
|
|
446
|
+
"""
|
|
447
|
+
with open(xyz_file, "r") as f:
|
|
448
|
+
first_line: str = f.readline().strip()
|
|
449
|
+
|
|
450
|
+
# XYZ の 1 行目はテンプレート内の原子数である。
|
|
451
|
+
atom_count: int = int(first_line)
|
|
452
|
+
if atom_count <= 0:
|
|
453
|
+
raise ValueError(f"XYZ ファイルの原子数が正ではない: {xyz_file}")
|
|
454
|
+
|
|
455
|
+
return atom_count
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def make_atomic_mixture_recipe(
|
|
459
|
+
symbols: Sequence[str],
|
|
460
|
+
ratios: Sequence[int],
|
|
461
|
+
total_atom_count: int,
|
|
462
|
+
xyz_files: Optional[Sequence[str]] = None,
|
|
463
|
+
) -> MaterialRecipe:
|
|
464
|
+
"""
|
|
465
|
+
複数元素の単原子テンプレートを作り、Packmol 用レシピを返す。
|
|
466
|
+
|
|
467
|
+
FeSiB のように、分子ユニットを使わず、Fe、Si、B の単原子をそれぞれ指定数だけ
|
|
468
|
+
ランダム配置する場合に使う。
|
|
469
|
+
|
|
470
|
+
total_atom_count は、最終セルに入れる総原子数である。
|
|
471
|
+
ratios の比に従い、各元素の原子数を決める。
|
|
472
|
+
例えば total_atom_count=100、ratios=[80, 9, 11] なら、Fe 80 個、Si 9 個、
|
|
473
|
+
B 11 個の単原子テンプレートを Packmol に配置させる recipe を作る。
|
|
474
|
+
"""
|
|
475
|
+
if len(symbols) != len(ratios):
|
|
476
|
+
raise ValueError("symbols と ratios の長さが一致していません。")
|
|
477
|
+
|
|
478
|
+
if total_atom_count <= 0:
|
|
479
|
+
raise ValueError("total_atom_count は正の整数である必要があります。")
|
|
480
|
+
|
|
481
|
+
ratio_sum: int = sum(ratios)
|
|
482
|
+
if ratio_sum <= 0:
|
|
483
|
+
raise ValueError("ratios の合計は正の整数である必要があります。")
|
|
484
|
+
|
|
485
|
+
if total_atom_count % ratio_sum != 0:
|
|
486
|
+
raise ValueError("total_atom_count は ratios の合計の整数倍である必要があります。")
|
|
487
|
+
|
|
488
|
+
# xyz_files を明示しない場合は、元素記号から Fe.xyz, Si.xyz のように自動命名する。
|
|
489
|
+
# FeSiB のような単原子混合系のサンプルでは、この規約で十分である。
|
|
490
|
+
if xyz_files is None:
|
|
491
|
+
actual_xyz_files: List[str] = []
|
|
492
|
+
for symbol in symbols:
|
|
493
|
+
actual_xyz_files.append(f"{symbol}.xyz")
|
|
494
|
+
else:
|
|
495
|
+
if len(symbols) != len(xyz_files):
|
|
496
|
+
raise ValueError("symbols と xyz_files の長さが一致していません。")
|
|
497
|
+
actual_xyz_files = list(xyz_files)
|
|
498
|
+
|
|
499
|
+
# total_atom_count が組成比の整数倍であるため、各元素の配置数は倍率で決まる。
|
|
500
|
+
multiplier: int = total_atom_count // ratio_sum
|
|
501
|
+
structures: List[StructureSpec] = []
|
|
502
|
+
|
|
503
|
+
for symbol, ratio, xyz_file in zip(symbols, ratios, actual_xyz_files):
|
|
504
|
+
# Fe80Si9B11 で total_atom_count=100 の場合、ratio がそのまま各元素数になる。
|
|
505
|
+
atom_count: int = ratio * multiplier
|
|
506
|
+
|
|
507
|
+
# 各元素の 1 原子テンプレートを作る。
|
|
508
|
+
write_single_atom_xyz(filename=xyz_file, element=symbol)
|
|
509
|
+
|
|
510
|
+
# その単原子テンプレートを atom_count 個配置する structure として recipe に登録する。
|
|
511
|
+
structures.append(StructureSpec(xyz_file=xyz_file, number=atom_count))
|
|
512
|
+
|
|
513
|
+
return MaterialRecipe(
|
|
514
|
+
total_atom_count=total_atom_count,
|
|
515
|
+
symbols=list(symbols),
|
|
516
|
+
ratios=list(ratios),
|
|
517
|
+
structures=structures,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def make_tetrahedral_unit_fnc_pairs(
|
|
522
|
+
center_index: int = 1,
|
|
523
|
+
vertex_indices: Optional[Sequence[int]] = None,
|
|
524
|
+
center_vertex_constraint_type: int = 1,
|
|
525
|
+
vertex_vertex_constraint_type: int = 2,
|
|
526
|
+
) -> List[Tuple[int, int, int]]:
|
|
527
|
+
"""
|
|
528
|
+
正四面体ユニット用の FNC 局所ペアを作る。
|
|
529
|
+
|
|
530
|
+
返り値は、テンプレート内の 1 始まり局所原子番号で表した
|
|
531
|
+
(local_i, local_j, constraint_type) のリストである。
|
|
532
|
+
PO4.xyz が P, O, O, O, O の順なら、P-O 4 本と O-O 6 本を返す。
|
|
533
|
+
実際の Packmol XYZ 番号や POSCAR/cfg 番号への変換は builder 側で行う。
|
|
534
|
+
"""
|
|
535
|
+
if vertex_indices is None:
|
|
536
|
+
vertex_indices = [2, 3, 4, 5]
|
|
537
|
+
|
|
538
|
+
vertex_index_list: List[int] = list(vertex_indices)
|
|
539
|
+
if len(vertex_index_list) != 4:
|
|
540
|
+
raise ValueError("正四面体ユニットの頂点原子は 4 個で指定する。")
|
|
541
|
+
|
|
542
|
+
fnc_pairs: List[Tuple[int, int, int]] = []
|
|
543
|
+
|
|
544
|
+
for vertex_index in vertex_index_list:
|
|
545
|
+
fnc_pairs.append((center_index, vertex_index, center_vertex_constraint_type))
|
|
546
|
+
|
|
547
|
+
for i, vertex_i in enumerate(vertex_index_list):
|
|
548
|
+
for vertex_j in vertex_index_list[i + 1:]:
|
|
549
|
+
fnc_pairs.append((vertex_i, vertex_j, vertex_vertex_constraint_type))
|
|
550
|
+
|
|
551
|
+
return fnc_pairs
|
|
552
|
+
|
|
553
|
+
def _midpoint_distance(distance_range: Tuple[float, float], name: str) -> float:
|
|
554
|
+
"""
|
|
555
|
+
FNC の rmin/rmax から Packmol テンプレート用の代表距離を返す。
|
|
556
|
+
"""
|
|
557
|
+
rmin, rmax = distance_range
|
|
558
|
+
if rmin <= 0.0:
|
|
559
|
+
raise ValueError(f"{name} の rmin は正の値で指定する。")
|
|
560
|
+
if rmax <= 0.0:
|
|
561
|
+
raise ValueError(f"{name} の rmax は正の値で指定する。")
|
|
562
|
+
if rmin >= rmax:
|
|
563
|
+
raise ValueError(f"{name} は rmin < rmax で指定する。")
|
|
564
|
+
return 0.5 * (rmin + rmax)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def make_tetrahedral_unit_fnc_spec_from_ranges(
|
|
568
|
+
center_vertex_range: Tuple[float, float],
|
|
569
|
+
vertex_vertex_range: Tuple[float, float],
|
|
570
|
+
center_index: int = 1,
|
|
571
|
+
vertex_indices: Optional[Sequence[int]] = None,
|
|
572
|
+
center_vertex_constraint_type: int = 1,
|
|
573
|
+
vertex_vertex_constraint_type: int = 2,
|
|
574
|
+
geometry_distance_tolerance: float = 0.05,
|
|
575
|
+
) -> TetrahedralUnitFncSpec:
|
|
576
|
+
"""
|
|
577
|
+
FNC の rmin/rmax を一次情報として、正四面体ユニットの仕様を作る。
|
|
578
|
+
|
|
579
|
+
center_vertex_range は中心原子-頂点原子の FNC 距離範囲である。
|
|
580
|
+
vertex_vertex_range は頂点原子-頂点原子の FNC 距離範囲である。
|
|
581
|
+
|
|
582
|
+
Packmol に渡す PO4 などのテンプレート XYZ 用距離は、これらの範囲の
|
|
583
|
+
中央値から派生させる。したがって、Packmol 用距離と FNC 用距離範囲を
|
|
584
|
+
利用側で二重管理しない。
|
|
585
|
+
|
|
586
|
+
geometry_distance_tolerance は FNC の許容幅ではない。
|
|
587
|
+
FNC range の中央値から作った中心-頂点距離と頂点-頂点距離が、
|
|
588
|
+
正四面体として幾何学的に整合しているかを検査するためだけに使う。
|
|
589
|
+
"""
|
|
590
|
+
center_vertex_distance = _midpoint_distance(
|
|
591
|
+
center_vertex_range,
|
|
592
|
+
"center_vertex_range",
|
|
593
|
+
)
|
|
594
|
+
vertex_vertex_distance = _midpoint_distance(
|
|
595
|
+
vertex_vertex_range,
|
|
596
|
+
"vertex_vertex_range",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
_validate_regular_tetrahedron_distances(
|
|
600
|
+
center_vertex_distance=center_vertex_distance,
|
|
601
|
+
vertex_vertex_distance=vertex_vertex_distance,
|
|
602
|
+
distance_tolerance=geometry_distance_tolerance,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
fnc_pairs_in_template = make_tetrahedral_unit_fnc_pairs(
|
|
606
|
+
center_index=center_index,
|
|
607
|
+
vertex_indices=vertex_indices,
|
|
608
|
+
center_vertex_constraint_type=center_vertex_constraint_type,
|
|
609
|
+
vertex_vertex_constraint_type=vertex_vertex_constraint_type,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
return TetrahedralUnitFncSpec(
|
|
613
|
+
center_vertex_distance=center_vertex_distance,
|
|
614
|
+
vertex_vertex_distance=vertex_vertex_distance,
|
|
615
|
+
fnc_pairs_in_template=fnc_pairs_in_template,
|
|
616
|
+
fnc_distance_ranges={
|
|
617
|
+
center_vertex_constraint_type: center_vertex_range,
|
|
618
|
+
vertex_vertex_constraint_type: vertex_vertex_range,
|
|
619
|
+
},
|
|
620
|
+
)
|
|
621
|
+
|