Files
solidstate-tools/corner-sharing/utils/analyze_env_st.py
2025-09-22 11:18:39 +08:00

210 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# This code extracts the lithium environment of all of lithium sites provided in a structure file.
import os, sys
import numpy as np
import scipy
import argparse
from scipy.spatial import ConvexHull
from itertools import permutations
from pymatgen.core.structure import Structure
from pymatgen.core.periodic_table import *
from pymatgen.core.composition import *
from pymatgen.ext.matproj import MPRester
from pymatgen.io.vasp.outputs import *
from pymatgen.analysis.chemenv.coordination_environments.coordination_geometry_finder import LocalGeometryFinder
from pymatgen.analysis.chemenv.coordination_environments.structure_environments import LightStructureEnvironments
from pymatgen.analysis.chemenv.coordination_environments.chemenv_strategies import SimplestChemenvStrategy
from pymatgen.analysis.chemenv.coordination_environments.coordination_geometries import *
__author__ = "KyuJung Jun"
__version__ = "0.1"
__maintainer__ = "KyuJung Jun"
__email__ = "kjun@berkeley.edu"
__status__ = "Development"
'''
Input for the script : path to the structure file supported by Pymatgen
Structures with partial occupancy should be ordered or modified to full occupancy by Pymatgen.
'''
parser = argparse.ArgumentParser()
parser.add_argument('structure', help='path to the structure file supported by Pymatgen', nargs='?')
parser.add_argument('envtype', help='both, tet, oct, choosing which perfect environment to reference to', nargs='?')
args = parser.parse_args()
class HiddenPrints:
'''
class to reduce the output lines
'''
def __enter__(self):
self._original_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout.close()
sys.stdout = self._original_stdout
def non_elements(struct, sp='Li'):
"""
struct : 必须是一个有序结构
sp : the mobile specie
returns a new structure containing only the framework anions (O, S, N).
"""
anions_to_keep = {"O", "S", "N"}
stripped = struct.copy()
species_to_remove = [el.symbol for el in stripped.composition.elements
if el.symbol not in anions_to_keep]
if species_to_remove:
stripped.remove_species(species_to_remove)
return stripped
def site_env(coord, struct, sp="Li", envtype='both'):
'''
coord : Fractional coordinate of the target atom
struct : structure object from Pymatgen
sp : the mobile specie
envtype : This sets the reference perfect structure. 'both' compares CSM_tet and CSM_oct and assigns to the lower one.
'tet' refers to the perfect tetrahedron and 'oct' refers to the perfect octahedron
result : a dictionary of environment information
'''
stripped = non_elements(struct)
with_li = stripped.copy()
with_li.append(sp, coord, coords_are_cartesian=False, validate_proximity=False)
with_li = with_li.get_sorted_structure()
tet_oct_competition = []
if envtype == 'both' or envtype == 'tet':
for dist in np.linspace(1, 4, 601):
neigh = with_li.get_neighbors(with_li.sites[0], dist)
if len(neigh) < 4:
continue
elif len(neigh) > 4:
break
neigh_coords = [i.coords for i in neigh]
with HiddenPrints():
lgf = LocalGeometryFinder(only_symbols=["T:4"])
lgf.setup_structure(structure=with_li)
lgf.setup_local_geometry(isite=0, coords=neigh_coords)
try:
site_volume = ConvexHull(neigh_coords).volume
tet_env_list = []
for i in range(20):
tet_env = {'csm': lgf.get_coordination_symmetry_measures()['T:4']['csm'], 'vol': site_volume,
'type': 'tet'}
tet_env_list.append(tet_env)
tet_env = min(tet_env_list, key=lambda x: x['csm'])
tet_oct_competition.append(tet_env)
except Exception as e:
print(e)
print("This site cannot be recognized as tetrahedral site")
if len(neigh) == 4:
break
if envtype == 'both' or envtype == 'oct':
for dist in np.linspace(1, 4, 601):
neigh = with_li.get_neighbors(with_li.sites[0], dist)
if len(neigh) < 6:
continue
elif len(neigh) > 6:
break
neigh_coords = [i.coords for i in neigh]
with HiddenPrints():
lgf = LocalGeometryFinder(only_symbols=["O:6"], permutations_safe_override=False)
lgf.setup_structure(structure=with_li)
lgf.setup_local_geometry(isite=0, coords=neigh_coords)
try:
site_volume = ConvexHull(neigh_coords).volume
oct_env_list = []
for i in range(20):
'''
20 times sampled in case of the algorithm "APPROXIMATE_FALLBACK" is used. Large number of permutations
are performed, but the default value in the function "coordination_geometry_symmetry_measures_fallback_random"
(NRANDOM=10) is often too small. This is not a problem if algorithm of "SEPARATION_PLANE" is used.
'''
oct_env = {'csm': lgf.get_coordination_symmetry_measures()['O:6']['csm'], 'vol': site_volume,
'type': 'oct'}
oct_env_list.append(oct_env)
oct_env = min(oct_env_list, key=lambda x: x['csm'])
tet_oct_competition.append(oct_env)
except Exception as e:
print(e)
print("This site cannot be recognized as octahedral site")
if len(neigh) == 6:
break
if len(tet_oct_competition) == 0:
return {'csm': np.nan, 'vol': np.nan, 'type': 'Non_' + envtype}
elif len(tet_oct_competition) == 1:
return tet_oct_competition[0]
elif len(tet_oct_competition) == 2:
csm1 = tet_oct_competition[0]
csm2 = tet_oct_competition[1]
if csm1['csm'] > csm2['csm']:
return csm2
else:
return csm1
def extract_sites(struct, sp="Li", envtype='both'):
"""
struct : structure object from Pymatgen
envtype : 'tet', 'oct', or 'both'
sp : target element to analyze environment
"""
envlist = []
# --- 关键修改:直接遍历原始结构,即使它是无序的 ---
# 我们不再调用 get_sorted_structure()
# 我们只关心那些含有目标元素 sp 的位点
# 遍历每一个位点 (site)
for i, site in enumerate(struct):
# 检查当前位点的组分(site.species)中是否包含我们感兴趣的元素(sp)
# site.species.elements 返回该位点上的元素列表,例如 [Element Li, Element Fe]
# [el.symbol for el in site.species.elements] 将其转换为符号列表 ['Li', 'Fe']
site_elements = [el.symbol for el in site.species.elements]
if sp in site_elements:
# 如果找到了Li我们就对这个位点进行环境分析
# 注意:我们将原始的、可能无序的 struct 传递给 site_env
# 因为 site_env 内部的函数 (如 LocalGeometryFinder) 知道如何处理它
# 为了让下游函数(特别是 non_elements能够工作
# 我们在这里创建一个一次性的、临时的有序结构副本给它
# 这可以避免我们之前遇到的所有 'ordered structures only' 错误
temp_ordered_struct = struct.get_sorted_structure()
singleenv = site_env(site.frac_coords, temp_ordered_struct, sp, envtype)
envlist.append({'frac_coords': site.frac_coords, 'type': singleenv['type'], 'csm': singleenv['csm'],
'volume': singleenv['vol']})
if not envlist:
print(f"警告: 在结构中未找到元素 {sp} 的占位。")
return envlist
def export_envs(envlist, sp='Li', envtype='both', fname=None):
'''
envlist : list of dictionaries of environment information
fname : Output file name
'''
if not fname:
fname = "extracted_environment_info" + "_" + sp + "_" + envtype + ".dat"
with open(fname, 'w') as f:
f.write('List of environment information\n')
f.write('Species : ' + sp + "\n")
f.write('Envtype : ' + envtype + "\n")
for index, i in enumerate(envlist):
f.write("Site index " + str(index) + ": " + str(i) + '\n')
# struct = Structure.from_file("../data/0921/wjy_475.cif")
# site_info = extract_sites(struct, envtype="both")
# export_envs(site_info, sp="Li", envtype="both")