Initial commit: PS4-centered Li/I density analysis
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# Ignore everything by default.
|
||||||
|
*
|
||||||
|
|
||||||
|
# Keep Git control files.
|
||||||
|
!.gitignore
|
||||||
|
!README.org
|
||||||
|
|
||||||
|
# Keep analysis scripts.
|
||||||
|
!dump2cube.py
|
||||||
|
!cube2mayavi.py
|
||||||
|
|
||||||
|
# Keep selected example input/output files.
|
||||||
|
!050Li3PS4-050LiI.lammpstrj
|
||||||
|
!050Li3PS4-050LiI_PS4_I.cube
|
||||||
+500900
File diff suppressed because it is too large
Load Diff
+682678
File diff suppressed because it is too large
Load Diff
+194
@@ -0,0 +1,194 @@
|
|||||||
|
#+TITLE: Li2S-P2S5-LiI Glass: PS4-Centered Ion Density Analysis
|
||||||
|
#+AUTHOR: Minami Sakuma
|
||||||
|
#+OPTIONS: toc:2 num:nil
|
||||||
|
|
||||||
|
* Overview
|
||||||
|
|
||||||
|
This repository contains Python scripts for analyzing and visualizing
|
||||||
|
Li and I spatial probability distributions around =PS4^{3-}= units in
|
||||||
|
Li2S-P2S5-LiI glass trajectories.
|
||||||
|
|
||||||
|
The workflow consists of two steps:
|
||||||
|
|
||||||
|
1. =dump2cube.py=
|
||||||
|
- Reads a LAMMPS trajectory.
|
||||||
|
- Aligns each =PS4^{3-}= tetrahedron using the nearest I^- ion.
|
||||||
|
- Accumulates Li and I positions in the aligned coordinate system.
|
||||||
|
- Outputs three-dimensional probability-density data in Gaussian cube format.
|
||||||
|
|
||||||
|
2. =cube2mayavi.py=
|
||||||
|
- Reads the generated cube file.
|
||||||
|
- Visualizes the spatial probability density as an isosurface using Mayavi.
|
||||||
|
- Displays the reference =PS4^{3-}= tetrahedron.
|
||||||
|
|
||||||
|
The scripts are intended to analyze the local geometrical relationship
|
||||||
|
between I^- ions and =PS4^{3-}= units in Li2S-P2S5-LiI glasses.
|
||||||
|
|
||||||
|
* Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|---+---|
|
||||||
|
| =dump2cube.py= | Converts a LAMMPS trajectory into Li/I probability-density cube files. |
|
||||||
|
| =cube2mayavi.py= | Visualizes a cube file with Mayavi. |
|
||||||
|
| =050Li3PS4-050LiI.lammpstrj= | Example LAMMPS trajectory. |
|
||||||
|
| =050Li3PS4-050LiI_PS4_I.cube= | Example I^- probability-density cube file. |
|
||||||
|
|
||||||
|
* Requirements
|
||||||
|
|
||||||
|
The scripts require Python 3 and the following packages:
|
||||||
|
|
||||||
|
- NumPy
|
||||||
|
- Mayavi
|
||||||
|
- VTK
|
||||||
|
- Traits
|
||||||
|
- PyQt5 or PySide6, depending on the Mayavi installation
|
||||||
|
|
||||||
|
Example installation using conda:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
conda create -n ps4-density python=3.10
|
||||||
|
conda activate ps4-density
|
||||||
|
conda install -c conda-forge numpy mayavi pyqt
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Alternatively, NumPy can be installed using pip:
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
pip install numpy
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Mayavi installation is generally more stable with conda-forge.
|
||||||
|
|
||||||
|
* Input Trajectory Format
|
||||||
|
|
||||||
|
=dump2cube.py= assumes a LAMMPS trajectory containing the following
|
||||||
|
atom columns:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
ITEM: ATOMS id type element mol x y z
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
The trajectory must contain at least the following elements:
|
||||||
|
|
||||||
|
- Li
|
||||||
|
- P
|
||||||
|
- S
|
||||||
|
- I
|
||||||
|
|
||||||
|
The script assumes that P and S atoms belonging to the same
|
||||||
|
=PS4^{3-}= unit share the same molecule ID (=mol=).
|
||||||
|
|
||||||
|
* Analysis Procedure
|
||||||
|
|
||||||
|
For each trajectory frame:
|
||||||
|
|
||||||
|
1. Each P atom is selected as the center of a reference =PS4^{3-}= unit.
|
||||||
|
2. The four nearest I^- ions are identified.
|
||||||
|
3. The nearest I^- ion is used to define the orientation of the =PS4^{3-}= unit.
|
||||||
|
4. The =PS4^{3-}= tetrahedron is rotated into a common reference frame.
|
||||||
|
5. Li positions within the cutoff distance are accumulated.
|
||||||
|
6. The positions of the four nearest I^- ions are accumulated.
|
||||||
|
7. Three-dimensional histograms are written as cube files.
|
||||||
|
|
||||||
|
The reference orientation is defined as follows:
|
||||||
|
|
||||||
|
- The S atom farthest from the nearest I^- ion is aligned with the z axis.
|
||||||
|
- A second S atom is used to fix the rotation around the z axis.
|
||||||
|
|
||||||
|
* Usage
|
||||||
|
|
||||||
|
** Generate Cube Files
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
python dump2cube.py \
|
||||||
|
-i 050Li3PS4-050LiI.lammpstrj \
|
||||||
|
-m 160 160 160 \
|
||||||
|
-cut 8
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---+---|
|
||||||
|
| =-i=, =--trjfile= | Input LAMMPS trajectory file. |
|
||||||
|
| =-m=, =--mesh= | Number of grid points in x, y, and z directions. |
|
||||||
|
| =-cut=, =--cutoff= | Spatial cutoff radius in angstrom. |
|
||||||
|
|
||||||
|
Expected output files:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
050Li3PS4-050LiI_PS4_Li.cube
|
||||||
|
050Li3PS4-050LiI_PS4_I.cube
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
** Visualize I^- Probability Density
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
python cube2mayavi.py \
|
||||||
|
-i 050Li3PS4-050LiI_PS4_I.cube \
|
||||||
|
-atom I \
|
||||||
|
-iso 1.26483e-09
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Visualize Li+ Probability Density
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
python cube2mayavi.py \
|
||||||
|
-i 050Li3PS4-050LiI_PS4_Li.cube \
|
||||||
|
-atom Li \
|
||||||
|
-iso 1.0e-09
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The appropriate isovalue depends on the trajectory length, mesh size,
|
||||||
|
cutoff radius, and probability-density distribution. It should therefore
|
||||||
|
be adjusted for each dataset.
|
||||||
|
|
||||||
|
* Output
|
||||||
|
|
||||||
|
The cube files contain:
|
||||||
|
|
||||||
|
- A reference =PS4^{3-}= tetrahedron:
|
||||||
|
- P atom at the origin
|
||||||
|
- Four S atoms in the aligned coordinate system
|
||||||
|
- A three-dimensional spatial probability-density field for Li or I
|
||||||
|
|
||||||
|
The cube files can be visualized using:
|
||||||
|
|
||||||
|
- Mayavi
|
||||||
|
- VMD
|
||||||
|
- PyMOL
|
||||||
|
- ParaView
|
||||||
|
- Other software supporting Gaussian cube files
|
||||||
|
|
||||||
|
* Visualization Colors
|
||||||
|
|
||||||
|
The default visualization settings in =cube2mayavi.py= are:
|
||||||
|
|
||||||
|
| Object | Color |
|
||||||
|
|---+---|
|
||||||
|
| P | Purple |
|
||||||
|
| S | Yellow |
|
||||||
|
| Li probability density | Blue |
|
||||||
|
| I probability density | Red |
|
||||||
|
| P-S bonds | Gray |
|
||||||
|
|
||||||
|
* Notes
|
||||||
|
|
||||||
|
- The trajectory is treated using periodic boundary conditions.
|
||||||
|
- Coordinates are converted to fractional coordinates before alignment.
|
||||||
|
- The current implementation assumes an orthorhombic simulation box for
|
||||||
|
the trajectory parsing procedure.
|
||||||
|
- The script includes a triclinic-cell lattice conversion function, but
|
||||||
|
the exact input format should be checked before applying it to
|
||||||
|
triclinic LAMMPS trajectories.
|
||||||
|
- Large trajectory and cube files can exceed the standard GitHub file-size
|
||||||
|
limit. Git LFS is recommended when files are larger than 100 MB.
|
||||||
|
|
||||||
|
* Citation
|
||||||
|
|
||||||
|
If this repository is used in research, please cite the corresponding
|
||||||
|
publication or presentation describing the Li2S-P2S5-LiI glass analysis.
|
||||||
|
|
||||||
|
* License
|
||||||
|
|
||||||
|
This repository is intended for academic research use.
|
||||||
Executable
+246
@@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# ./cube2mayavi.py -i 050Li3PS4-050LiI_PS4_I.cube -atom I -iso 1.26483e-09
|
||||||
|
import numpy as np
|
||||||
|
from mayavi import mlab
|
||||||
|
ang_borr = 0.5291772109217
|
||||||
|
|
||||||
|
|
||||||
|
def read_cube(filename):
|
||||||
|
"""
|
||||||
|
.cubeファイルを読み込み、原子座標と3次元ボクセルデータを返す関数
|
||||||
|
"""
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
# ヘッダーの読み飛ばし(最初の2行はコメント)
|
||||||
|
f.readline()
|
||||||
|
f.readline()
|
||||||
|
|
||||||
|
# 3行目: 原子の数 と 原点座標
|
||||||
|
line = f.readline().split()
|
||||||
|
natoms = abs(int(line[0])) # 原子の数
|
||||||
|
origin = np.array([float(x) for x in line[1:4]]) # 原点
|
||||||
|
|
||||||
|
# 4-6行目: グリッドの分割数(N)とベクトル(X, Y, Z軸)
|
||||||
|
line = f.readline().split()
|
||||||
|
nx, x_vec = int(line[0]), np.array([float(x) for x in line[1:4]])
|
||||||
|
line = f.readline().split()
|
||||||
|
ny, y_vec = int(line[0]), np.array([float(x) for x in line[1:4]])
|
||||||
|
line = f.readline().split()
|
||||||
|
nz, z_vec = int(line[0]), np.array([float(x) for x in line[1:4]])
|
||||||
|
|
||||||
|
# 原子座標の読み込み
|
||||||
|
atoms = []
|
||||||
|
for _ in range(natoms):
|
||||||
|
line = f.readline().split()
|
||||||
|
atoms.append([float(x) for x in line[2:5]])
|
||||||
|
atoms = np.array(atoms)
|
||||||
|
|
||||||
|
# ボクセルデータの読み込み
|
||||||
|
data = []
|
||||||
|
for line in f:
|
||||||
|
data.extend([float(x) for x in line.split()])
|
||||||
|
|
||||||
|
vol_data = np.array(data).reshape(nx, ny, nz)
|
||||||
|
|
||||||
|
return vol_data, atoms, origin, (x_vec, y_vec, z_vec)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_radial_scale(max_dist=10.0, step=1.0, axis='x'):
|
||||||
|
"""
|
||||||
|
原点から動径方向に目盛り(定規)を描画する
|
||||||
|
"""
|
||||||
|
if axis == 'y':
|
||||||
|
vec = np.array([0, 1, 0])
|
||||||
|
tick_dir = np.array([0.2, 0, 0])
|
||||||
|
elif axis == 'z':
|
||||||
|
vec = np.array([0, 0, 1])
|
||||||
|
tick_dir = np.array([0.2, 0, 0])
|
||||||
|
else: # default x
|
||||||
|
vec = np.array([1, 0, 0])
|
||||||
|
tick_dir = np.array([0, 0, 0.2])
|
||||||
|
|
||||||
|
# メインの直線を引く
|
||||||
|
end_point = vec * max_dist
|
||||||
|
mlab.plot3d([0, end_point[0]], [0, end_point[1]], [0, end_point[2]],
|
||||||
|
tube_radius=0.02, color=(0, 0, 0))
|
||||||
|
|
||||||
|
# 目盛りと数字を配置
|
||||||
|
for r in np.arange(step, max_dist + step, step):
|
||||||
|
pos = vec * r
|
||||||
|
t_start = pos - tick_dir
|
||||||
|
t_end = pos + tick_dir
|
||||||
|
|
||||||
|
mlab.plot3d([t_start[0], t_end[0]],
|
||||||
|
[t_start[1], t_end[1]],
|
||||||
|
[t_start[2], t_end[2]],
|
||||||
|
tube_radius=0.02, color=(0, 0, 0))
|
||||||
|
|
||||||
|
text_pos = pos + tick_dir * 1.5
|
||||||
|
mlab.text3d(text_pos[0], text_pos[1], text_pos[2],
|
||||||
|
f"{int(r)}", scale=0.25, color=(0, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def change_view(azimuth=30, elevation=60):
|
||||||
|
mlab.view(azimuth=azimuth, elevation=elevation)
|
||||||
|
|
||||||
|
|
||||||
|
def Li_visualize(filename, iso_val=None, radius_cutoff=5.0):
|
||||||
|
print(f"Reading {filename}...")
|
||||||
|
vol_data, atoms, origin, vectors = read_cube(filename)
|
||||||
|
atoms, origin_ang = atoms * ang_borr, origin * ang_borr
|
||||||
|
|
||||||
|
dx = np.linalg.norm(vectors[0]) * ang_borr
|
||||||
|
dy = np.linalg.norm(vectors[1]) * ang_borr
|
||||||
|
dz = np.linalg.norm(vectors[2]) * ang_borr
|
||||||
|
|
||||||
|
nx, ny, nz = vol_data.shape
|
||||||
|
i, j, k = np.mgrid[0:nx, 0:ny, 0:nz]
|
||||||
|
|
||||||
|
X = origin_ang[0] + i * dx
|
||||||
|
Y = origin_ang[1] + j * dy
|
||||||
|
Z = origin_ang[2] + k * dz
|
||||||
|
R = np.sqrt(X**2 + Y**2 + Z**2)
|
||||||
|
|
||||||
|
vol_inner = np.where(R <= 5, vol_data, 0.0)
|
||||||
|
vol_outer = np.where((R < 9) & (R > 5), vol_data, 0.0)
|
||||||
|
|
||||||
|
mlab.figure(bgcolor=(1, 1, 1), size=(800, 600))
|
||||||
|
|
||||||
|
if args.iso_val is None:
|
||||||
|
iso_val = np.std(vol_data) * 2.0
|
||||||
|
else:
|
||||||
|
iso_val = args.iso_val
|
||||||
|
|
||||||
|
print(f"Plotting isosurface at value: +/- {iso_val:.4f}")
|
||||||
|
print("max_value: ", np.max(vol_data))
|
||||||
|
print("min_value: ", np.min(vol_data))
|
||||||
|
|
||||||
|
# --- 確率密度 内側 ---
|
||||||
|
src_in = mlab.pipeline.scalar_field(vol_inner)
|
||||||
|
src_in.spacing = [dx, dy, dz]
|
||||||
|
src_in.origin = origin_ang
|
||||||
|
obj_in = mlab.pipeline.iso_surface(src_in, contours=[iso_val],
|
||||||
|
opacity=0.4, color=(0, 0.2, 0.8))
|
||||||
|
|
||||||
|
# --- 確率密度 外側 ---
|
||||||
|
src_out = mlab.pipeline.scalar_field(vol_outer)
|
||||||
|
src_out.spacing = [dx, dy, dz]
|
||||||
|
src_out.origin = origin_ang
|
||||||
|
obj_out = mlab.pipeline.iso_surface(src_out, contours=[iso_val],
|
||||||
|
opacity=0.2, color=(0.7, 0.1, 0.1))
|
||||||
|
|
||||||
|
# --- 原子の描画 ---
|
||||||
|
mlab.points3d(atoms[0, 0], atoms[0, 1], atoms[0, 2],
|
||||||
|
scale_factor=0.8, color=(0.576, 0.439, 0.8), resolution=20)
|
||||||
|
mlab.points3d(atoms[1:, 0], atoms[1:, 1], atoms[1:, 2],
|
||||||
|
scale_factor=0.8, color=(1, 1, 0), resolution=20)
|
||||||
|
|
||||||
|
# --- 結合の描画 ---
|
||||||
|
p_coord = atoms[0]
|
||||||
|
s_coords = atoms[1:]
|
||||||
|
for s_coord in s_coords:
|
||||||
|
mlab.plot3d([p_coord[0], s_coord[0]],
|
||||||
|
[p_coord[1], s_coord[1]],
|
||||||
|
[p_coord[2], s_coord[2]],
|
||||||
|
tube_radius=0.1, color=(0.6, 0.6, 0.6), opacity=1.0)
|
||||||
|
|
||||||
|
mlab.points3d(0, 0, 0, scale_factor=6.6,
|
||||||
|
mode='sphere', color=(0, 0, 0), opacity=0.05, resolution=50)
|
||||||
|
mlab.points3d(0, 0, 0, scale_factor=14,
|
||||||
|
mode='sphere', color=(0, 0, 0), opacity=0.05, resolution=50)
|
||||||
|
|
||||||
|
mlab.axes()
|
||||||
|
return obj_in, obj_out
|
||||||
|
|
||||||
|
|
||||||
|
def I_visualize(filename, iso_val=None, cutoff=8):
|
||||||
|
"""
|
||||||
|
1つのIのcubeファイルを読み込んで可視化する
|
||||||
|
"""
|
||||||
|
print(f"Reading {filename}...")
|
||||||
|
vol_data, atoms, origin, vectors = read_cube(filename)
|
||||||
|
atoms, origin_ang = atoms * ang_borr, origin * ang_borr
|
||||||
|
|
||||||
|
dx = np.linalg.norm(vectors[0]) * ang_borr
|
||||||
|
dy = np.linalg.norm(vectors[1]) * ang_borr
|
||||||
|
dz = np.linalg.norm(vectors[2]) * ang_borr
|
||||||
|
|
||||||
|
mlab.figure(bgcolor=(1, 1, 1), size=(800, 600))
|
||||||
|
|
||||||
|
if iso_val is None:
|
||||||
|
if 'args' in globals() and hasattr(args, 'iso_val') and args.iso_val is not None:
|
||||||
|
iso_val = args.iso_val
|
||||||
|
else:
|
||||||
|
iso_val = np.std(vol_data) * 2.0
|
||||||
|
|
||||||
|
print(f"Plotting isosurface at value: +/- {iso_val:.4f}")
|
||||||
|
|
||||||
|
# --- カットオフによるデータの球状マスク処理 ---
|
||||||
|
if cutoff is not None:
|
||||||
|
nx, ny, nz = vol_data.shape
|
||||||
|
x = origin_ang[0] + np.arange(nx) * dx
|
||||||
|
y = origin_ang[1] + np.arange(ny) * dy
|
||||||
|
z = origin_ang[2] + np.arange(nz) * dz
|
||||||
|
X, Y, Z = np.meshgrid(x, y, z, indexing='ij')
|
||||||
|
R = np.sqrt(X**2 + Y**2 + Z**2)
|
||||||
|
vol_data[R > cutoff] = 0.0
|
||||||
|
|
||||||
|
print("max_value: ", np.max(vol_data))
|
||||||
|
print("min_value: ", np.min(vol_data))
|
||||||
|
|
||||||
|
# --- 確率密度 ---
|
||||||
|
src_in = mlab.pipeline.scalar_field(vol_data)
|
||||||
|
src_in.spacing = [dx, dy, dz]
|
||||||
|
src_in.origin = origin_ang
|
||||||
|
|
||||||
|
# 赤色で表示
|
||||||
|
obj = mlab.pipeline.iso_surface(src_in, contours=[iso_val], opacity=0.3,
|
||||||
|
color=(0.850, 0.058, 0.058))
|
||||||
|
|
||||||
|
# --- 原子の描画 ---
|
||||||
|
mlab.points3d(atoms[0, 0], atoms[0, 1], atoms[0, 2],
|
||||||
|
scale_factor=0.8, color=(0.576, 0.439, 0.8), resolution=20)
|
||||||
|
mlab.points3d(atoms[1:, 0], atoms[1:, 1], atoms[1:, 2],
|
||||||
|
scale_factor=0.8, color=(1, 1, 0), resolution=20)
|
||||||
|
|
||||||
|
# --- 結合の描画 ---
|
||||||
|
p_coord = atoms[0]
|
||||||
|
s_coords = atoms[1:]
|
||||||
|
for s_coord in s_coords:
|
||||||
|
mlab.plot3d([p_coord[0], s_coord[0]],
|
||||||
|
[p_coord[1], s_coord[1]],
|
||||||
|
[p_coord[2], s_coord[2]],
|
||||||
|
tube_radius=0.1, color=(0.6, 0.6, 0.6), opacity=1.0)
|
||||||
|
|
||||||
|
mlab.points3d(0, 0, 0, scale_factor=11,
|
||||||
|
mode='sphere', color=(0, 0, 0), opacity=0.05, resolution=50)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
# --- 実行部分 ---
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
description = """This is a test program"""
|
||||||
|
par = argparse.ArgumentParser(description=description)
|
||||||
|
|
||||||
|
par.add_argument('-i', '--infiles', default="", required=True, nargs="+",
|
||||||
|
help='input file')
|
||||||
|
par.add_argument('-atom', '--atom', default="", required=True,
|
||||||
|
choices=['Li', 'I'],
|
||||||
|
help='atom')
|
||||||
|
par.add_argument('-iso', '--iso_val', required=False, type=float,
|
||||||
|
help='しきい値')
|
||||||
|
args = par.parse_args()
|
||||||
|
|
||||||
|
if args.atom == "Li":
|
||||||
|
obj_in, obj_out = Li_visualize(args.infiles[0])
|
||||||
|
mlab.show()
|
||||||
|
elif args.atom == "I":
|
||||||
|
if len(args.infiles) > 1:
|
||||||
|
print("Warning: Multiple files were passed, but only the first one will be read.")
|
||||||
|
obj = I_visualize(args.infiles[0])
|
||||||
|
mlab.show()
|
||||||
|
else:
|
||||||
|
print("atom error")
|
||||||
Executable
+300
@@ -0,0 +1,300 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# ./dump2cube.py -i 050Li3PS4-050LiI.lammpstrj -m 160 160 160 -cut 8
|
||||||
|
import numpy as np
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
description = """This is a test program"""
|
||||||
|
par = argparse.ArgumentParser(description=description)
|
||||||
|
|
||||||
|
par.add_argument('-i', '--trjfile', default="", required=True,
|
||||||
|
help='input trjfile')
|
||||||
|
par.add_argument('-m', '--mesh', default=(30, 30, 30), required=False,
|
||||||
|
nargs=3, type=int, help='mesh grid')
|
||||||
|
par.add_argument('-cut', '--cutoff', default=7, required=False,
|
||||||
|
type=float, help='mesh grid')
|
||||||
|
|
||||||
|
args = par.parse_args()
|
||||||
|
ang_borr = 0.5291772109217
|
||||||
|
dirname = os.path.dirname(__file__)
|
||||||
|
|
||||||
|
|
||||||
|
class LammpsTrj():
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
trjファイルを読み込んで、原子数、lattice、ステップ数を格納
|
||||||
|
"""
|
||||||
|
with open(args.trjfile) as o:
|
||||||
|
data = o.read().split()
|
||||||
|
self.atoms = int(data[7]) # 原子数
|
||||||
|
data = np.array(data).reshape(-1, self.atoms*7+32)
|
||||||
|
self.lattices = data[:, 17:23].astype(float) # (step, 6)
|
||||||
|
self.data = data[:, 32:] # 座標データ
|
||||||
|
self.steps = data.shape[0]
|
||||||
|
self.mesh = args.mesh
|
||||||
|
self.cutoff = args.cutoff
|
||||||
|
|
||||||
|
def setLattice(self, lat):
|
||||||
|
"""
|
||||||
|
latticeの形によって操作を分岐
|
||||||
|
self.M, self.M_を作成
|
||||||
|
"""
|
||||||
|
if lat.shape[0] == 6:
|
||||||
|
M = np.array([[lat[1]-lat[0], 0, 0],
|
||||||
|
[0, lat[3]-lat[2], 0],
|
||||||
|
[0, 0, lat[5]-lat[4]]]).astype(float)
|
||||||
|
if lat.shape[0] == 9:
|
||||||
|
xlo_bound, xhi_bound, xy = lat[0, 0], lat[0, 1], lat[0, 2]
|
||||||
|
ylo_bound, yhi_bound, xz = lat[1, 0], lat[1, 1], lat[1, 2]
|
||||||
|
zlo_bound, zhi_bound, yz = lat[2, 0], lat[2, 1], lat[2, 2]
|
||||||
|
xlo = xlo_bound - np.min([0.0, xy, xz, xy+xz])
|
||||||
|
xhi = xhi_bound - np.max([0.0, xy, xz, xy+xz])
|
||||||
|
ylo = ylo_bound - np.min([0.0, yz])
|
||||||
|
yhi = yhi_bound - np.max([0.0, yz])
|
||||||
|
zlo = zlo_bound
|
||||||
|
zhi = zhi_bound
|
||||||
|
lx = xhi - xlo
|
||||||
|
ly = yhi - ylo
|
||||||
|
lz = zhi - zlo
|
||||||
|
a = lx
|
||||||
|
b = np.sqrt(ly**2 + xy**2)
|
||||||
|
c = np.sqrt(lz**2 + xz**2 + yz**2)
|
||||||
|
alpha = np.arccos((xy*xz + ly*yz)/b/c)
|
||||||
|
beta = np.arccos(xz/c)
|
||||||
|
gamma = np.arccos(xy/b)
|
||||||
|
v1 = [a, 0, 0]
|
||||||
|
v2 = [b*np.cos(gamma), b*np.sin(gamma), 0]
|
||||||
|
v3 = [c*np.cos(beta),
|
||||||
|
c*(np.cos(alpha)-np.cos(beta)*np.cos(gamma))/np.sin(gamma),
|
||||||
|
c*np.sqrt(1+2*np.cos(alpha)*np.cos(beta)*np.cos(gamma)
|
||||||
|
- np.cos(alpha)**2-np.cos(beta)**2
|
||||||
|
- np.cos(gamma)**2 / np.sin(gamma))]
|
||||||
|
M = np.array([v1, v2, v3])
|
||||||
|
return M
|
||||||
|
|
||||||
|
def getOnestep(self, step):
|
||||||
|
"""
|
||||||
|
引数のstepにおける座標を、原子ごとにself.elems(辞書)に格納する
|
||||||
|
self.elemsの座標は分率座標
|
||||||
|
"""
|
||||||
|
step_data = self.data[step, :].reshape(self.atoms, -1) # (atoms, 6)
|
||||||
|
self.M = self.setLattice(self.lattices[step])
|
||||||
|
self.M_ = np.linalg.inv(self.M)
|
||||||
|
self.elems = {}
|
||||||
|
for e in np.unique(step_data[:, 2]):
|
||||||
|
d = step_data[step_data[:, 2] == e, 3:].astype(float)
|
||||||
|
dxyz = d[:, 0:3]
|
||||||
|
dxyz = dxyz @ self.M_
|
||||||
|
dxyz = dxyz - np.floor(dxyz)
|
||||||
|
d[:, 0:3] = dxyz
|
||||||
|
self.elems[e] = d # 元素ごとに結晶座標とmol番号を格納
|
||||||
|
return self.elems
|
||||||
|
|
||||||
|
def makeCube(self, trjfile):
|
||||||
|
"""
|
||||||
|
すべてのP原子について処理を行う。
|
||||||
|
P原子ごとに最も近い4つのI原子を選出し、回転・アライメントを行う。
|
||||||
|
"""
|
||||||
|
# Li用の座標リスト
|
||||||
|
self.li_coords_list = []
|
||||||
|
|
||||||
|
# I用のリスト (全てまとめて格納)
|
||||||
|
self.i_coords_list = []
|
||||||
|
|
||||||
|
# ヒストグラム結果格納用
|
||||||
|
self.li_hist = None
|
||||||
|
self.i_hist = None
|
||||||
|
|
||||||
|
count_p = 0 # 処理したP原子の総数カウント用
|
||||||
|
|
||||||
|
for step in range(self.steps):
|
||||||
|
print(f"processing {step} step")
|
||||||
|
self.elems = self.getOnestep(step)
|
||||||
|
elems_ = {k: v.copy() for k, v in self.elems.items()} # copy
|
||||||
|
|
||||||
|
# 全てのP原子に対してループ処理を行う
|
||||||
|
for p_data in elems_["P"]:
|
||||||
|
|
||||||
|
# 全てのI原子の中から、このP原子に最も近い4つのI原子を特定する
|
||||||
|
i_diffs = elems_["I"][:, 0:3] - p_data[0:3]
|
||||||
|
i_diffs = i_diffs - np.around(i_diffs) # 周期境界条件の考慮(分率)
|
||||||
|
i_diffs_abs = i_diffs @ self.M # 絶対座標へ変換
|
||||||
|
distances = np.linalg.norm(i_diffs_abs, axis=1)
|
||||||
|
|
||||||
|
sorted_indices = np.argsort(distances)
|
||||||
|
closest_Is = elems_["I"][sorted_indices[:4]] # 近い順に4つ抽出
|
||||||
|
|
||||||
|
nearest_I_coords = closest_Is[0] # 回転定義に使用する「最も近いI」
|
||||||
|
|
||||||
|
# S原子の処理 (同じmol番号のSを取得)
|
||||||
|
s_data = elems_["S"][elems_["S"][:, 3] == p_data[3]]
|
||||||
|
|
||||||
|
# 1つのPS4に注目して、Pを原点に配置するための距離計算
|
||||||
|
si_diff = s_data[:, 0:3] - nearest_I_coords[0:3]
|
||||||
|
si_diff = si_diff - np.around(si_diff)
|
||||||
|
si_diff = np.linalg.norm(si_diff, axis=1)
|
||||||
|
|
||||||
|
# 最も遠いSをz軸上に配置するため、そのインデックスを取得
|
||||||
|
n_S_idx = np.argmax(si_diff)
|
||||||
|
# その他のインデックスを取得
|
||||||
|
s_indices = [0, 1, 2, 3]
|
||||||
|
s_indices.remove(n_S_idx)
|
||||||
|
n2_S_idx = s_indices[0]
|
||||||
|
|
||||||
|
s_xyz = s_data[:, 0:3] - p_data[0:3]
|
||||||
|
s_xyz = s_xyz - np.round(s_xyz)
|
||||||
|
s_xyz = s_xyz @ self.M # 絶対座標へ
|
||||||
|
|
||||||
|
# 1つのSをyz平面上に配置(z軸周り回転)
|
||||||
|
theta = np.arctan2(s_xyz[n_S_idx][0], s_xyz[n_S_idx][1])
|
||||||
|
self.Mat_z = np.array([[np.cos(-theta), np.sin(-theta), 0],
|
||||||
|
[-np.sin(-theta), np.cos(-theta), 0],
|
||||||
|
[0, 0, 1]])
|
||||||
|
s_xyz = (self.Mat_z @ s_xyz.T).T
|
||||||
|
|
||||||
|
# 1つのSをz軸上に配置(x軸周り回転)
|
||||||
|
theta2 = np.arctan2(s_xyz[n_S_idx][1], s_xyz[n_S_idx][2])
|
||||||
|
self.Mat_x = np.array([[1, 0, 0],
|
||||||
|
[0, np.cos(-theta2), np.sin(-theta2)],
|
||||||
|
[0, -np.sin(-theta2), np.cos(-theta2)]])
|
||||||
|
s_xyz = (self.Mat_x @ s_xyz.T).T
|
||||||
|
|
||||||
|
# もう1つのSをyz平面上に配置(z軸周り回転)
|
||||||
|
theta3 = np.arctan2(s_xyz[n2_S_idx][0], s_xyz[n2_S_idx][1])
|
||||||
|
self.Mat_z2 = np.array([[np.cos(-theta3), np.sin(-theta3), 0],
|
||||||
|
[-np.sin(-theta3), np.cos(-theta3), 0],
|
||||||
|
[0, 0, 1]])
|
||||||
|
s_xyz = (self.Mat_z2 @ s_xyz.T).T
|
||||||
|
self.s_xyz = s_xyz
|
||||||
|
|
||||||
|
# PS4座標保存 (Output用)
|
||||||
|
p_xyz = np.array([0, 0, 0])
|
||||||
|
self.ps4_coord = np.vstack((p_xyz, s_xyz))
|
||||||
|
self.ps4_coord = self.ps4_coord / ang_borr
|
||||||
|
|
||||||
|
# 座標回転関数の定義
|
||||||
|
def rotate_coords(coords_fractional):
|
||||||
|
if len(coords_fractional) == 0:
|
||||||
|
return np.array([])
|
||||||
|
rot_xyz = coords_fractional[:, 0:3] - p_data[0:3]
|
||||||
|
rot_xyz = rot_xyz - np.round(rot_xyz)
|
||||||
|
rot_xyz = rot_xyz @ self.M
|
||||||
|
rot_xyz = (self.Mat_z @ rot_xyz.T).T
|
||||||
|
rot_xyz = (self.Mat_x @ rot_xyz.T).T
|
||||||
|
rot_xyz = (self.Mat_z2 @ rot_xyz.T).T
|
||||||
|
return rot_xyz
|
||||||
|
|
||||||
|
# ヒストグラム範囲
|
||||||
|
bounds = [[-(self.cutoff)/ang_borr,
|
||||||
|
(self.cutoff)/ang_borr]] * 3
|
||||||
|
|
||||||
|
# --- Liの処理: 座標をリストに追加 ---
|
||||||
|
segment_xyz_Li = []
|
||||||
|
for xyz in self.elems["Li"]:
|
||||||
|
diff = xyz[0:3] - p_data[0:3]
|
||||||
|
diff = diff - np.around(diff)
|
||||||
|
diff = diff @ self.M
|
||||||
|
diff = np.linalg.norm(diff)
|
||||||
|
for xyzdata in xyz[diff < self.cutoff, :]:
|
||||||
|
segment_xyz_Li.append(xyzdata)
|
||||||
|
segment_xyz_Li = np.array(segment_xyz_Li, dtype=float)
|
||||||
|
|
||||||
|
if len(segment_xyz_Li) > 0:
|
||||||
|
rotationed_Li = rotate_coords(segment_xyz_Li)
|
||||||
|
self.li_coords_list.extend(rotationed_Li.tolist())
|
||||||
|
|
||||||
|
# --- Iの処理: 抽出した4つのIを回転させてまとめてリストに追加 ---
|
||||||
|
rotationed_Is = rotate_coords(closest_Is)
|
||||||
|
self.i_coords_list.extend(rotationed_Is.tolist())
|
||||||
|
|
||||||
|
count_p += 1
|
||||||
|
|
||||||
|
# ループ終了後にまとめてヒストグラム計算
|
||||||
|
print("Calculating Histograms...")
|
||||||
|
volume = (self.cutoff*2)**3
|
||||||
|
bounds = [[-(self.cutoff)/ang_borr, (self.cutoff)/ang_borr]] * 3
|
||||||
|
|
||||||
|
# Li
|
||||||
|
if len(self.li_coords_list) > 0:
|
||||||
|
print("num_Li: ", len(self.li_coords_list))
|
||||||
|
li_arr = np.array(self.li_coords_list) / ang_borr
|
||||||
|
li_hist, _ = np.histogramdd(li_arr, bins=self.mesh, range=bounds)
|
||||||
|
self.li_hist = li_hist.ravel() / len(self.li_coords_list) / volume
|
||||||
|
print("sum_Li: ", np.sum(self.li_hist) * volume)
|
||||||
|
|
||||||
|
# I (All 4 atoms combined)
|
||||||
|
if len(self.i_coords_list) > 0:
|
||||||
|
print("num_I: ", len(self.i_coords_list))
|
||||||
|
i_arr = np.array(self.i_coords_list) / ang_borr
|
||||||
|
i_hist, _ = np.histogramdd(i_arr, bins=self.mesh, range=bounds)
|
||||||
|
self.i_hist = i_hist.ravel() / len(self.i_coords_list) / volume
|
||||||
|
print("sum_I: ", np.sum(self.i_hist) * volume)
|
||||||
|
|
||||||
|
def outputCube(self):
|
||||||
|
output_start_time = time.time()
|
||||||
|
base = re.match(r"(\d{3}.*?LiI).*?",
|
||||||
|
args.trjfile.split("/")[-1]).group(1)
|
||||||
|
self.atomsDic = {'I': '53', 'Li': '3', 'P': '15', 'S': '16'}
|
||||||
|
|
||||||
|
if self.li_hist is None and self.i_hist is None:
|
||||||
|
print("No data found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_header():
|
||||||
|
body = f"created from {__file__}, {args}\n"
|
||||||
|
body += "Contains the selected quantity on a FFT grid\n"
|
||||||
|
origin = [-(self.cutoff)/ang_borr] * 3
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
5, *origin)
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
self.mesh[0], (self.cutoff*2)/self.mesh[0]/ang_borr, 0, 0)
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
self.mesh[1], 0, (self.cutoff*2)/self.mesh[1]/ang_borr, 0)
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
self.mesh[2], 0, 0, (self.cutoff*2)/self.mesh[2]/ang_borr)
|
||||||
|
# Pの座標
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
int(self.atomsDic["P"]), float(self.atomsDic["P"]), *self.ps4_coord[0])
|
||||||
|
# Sの座標
|
||||||
|
for s_coord in self.ps4_coord[1:5]:
|
||||||
|
body += "{:>5d}{:>12.7f}{:>12.7f}{:>12.7f}{:>12.7f}\n".format(
|
||||||
|
int(self.atomsDic["S"]), float(self.atomsDic["S"]), *s_coord)
|
||||||
|
return body
|
||||||
|
|
||||||
|
# Liの出力
|
||||||
|
if self.li_hist is not None:
|
||||||
|
body = get_header()
|
||||||
|
for idx, r in enumerate(self.li_hist):
|
||||||
|
if idx % 6 == 5:
|
||||||
|
body += "{:>13.5E}\n".format(r)
|
||||||
|
else:
|
||||||
|
body += "{:>13.5E}".format(r)
|
||||||
|
|
||||||
|
outfile = f"{dirname}/{base}_PS4_Li.cube"
|
||||||
|
with open(outfile, "w") as o:
|
||||||
|
o.write(body)
|
||||||
|
print(f"{outfile} was created.")
|
||||||
|
|
||||||
|
# Iの出力 (まとめて1ファイルに出力)
|
||||||
|
if self.i_hist is not None:
|
||||||
|
body = get_header()
|
||||||
|
for idx, r in enumerate(self.i_hist):
|
||||||
|
if idx % 6 == 5:
|
||||||
|
body += "{:>13.5E}\n".format(r)
|
||||||
|
else:
|
||||||
|
body += "{:>13.5E}".format(r)
|
||||||
|
|
||||||
|
outfile = f"{dirname}/{base}_PS4_I.cube"
|
||||||
|
with open(outfile, "w") as o:
|
||||||
|
o.write(body)
|
||||||
|
print(f"{outfile} was created.")
|
||||||
|
|
||||||
|
output_end_time = time.time()
|
||||||
|
print(f"output_time : {output_end_time - output_start_time} s")
|
||||||
|
|
||||||
|
|
||||||
|
trj = LammpsTrj()
|
||||||
|
trj.makeCube(args.trjfile)
|
||||||
|
trj.outputCube()
|
||||||
Reference in New Issue
Block a user