-
Notifications
You must be signed in to change notification settings - Fork 0
/
slabs.py
265 lines (202 loc) · 12.3 KB
/
slabs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import subprocess
import importlib
def check_dependency(dependency):
try:
importlib.import_module(dependency)
except ImportError:
print(f"{dependency} is not installed. Installing...")
install_dependency(dependency)
def install_dependency(dependency):
subprocess.check_call(["pip", "install", dependency])
print(f"{dependency} has been installed successfully.")
# Check and install dependencies
check_dependency("matplotlib")
check_dependency("pymatgen")
import re
import warnings
import sys
from matplotlib import pyplot as plt
from pymatgen.io.vasp.inputs import Poscar
from pymatgen.core.structure import Structure
from pymatgen.core.surface import *
from pymatgen.core.lattice import Lattice
from pymatgen.analysis.adsorption import *
from pymatgen.transformations.standard_transformations import AutoOxiStateDecorationTransformation
from pymatgen.transformations.standard_transformations import RotationTransformation
from collections import defaultdict
warnings.simplefilter("ignore", UserWarning)
header = '''
The Pennsylvania State University
╔═╗┬─┐┌─┐┌─┐ ╔╦╗┌─┐┌─┐┌┬┐┌─┐|┌─┐⠀⠀⠀
╠═╝├┬┘│ │├┤ ║║┌─┘├─┤ ││├┤ └─┐
╩ ┴└─└─┘└ ═╩╝└─┘┴ ┴─┴┘└─┘ └─┘
╔╦╗╔╦╗╔╦╗ ╔═╗┬─┐┌─┐┬ ┬┌─┐
║║║║║║ ║ ║ ╦├┬┘│ ││ │├─┘
╩ ╩╩ ╩ ╩ ╚═╝┴└─└─┘└─┘┴
slabs.py (07.17.2023) Ricardo Amaral
########################################'''
def convert_poscar_coords(filename):
# Read the POSCAR file
poscar = Poscar.from_file(filename)
# Convert the fractional coordinates to cartesian coordinates
cart_coords = poscar.structure.lattice.get_cartesian_coords(poscar.structure.frac_coords)
# Read the POSCAR file into a list of lines
with open(filename, 'r') as f:
lines = f.readlines()
# Find the line index where the coordinates start
coord_start = None
for i, line in enumerate(lines):
if 'irect' in line or 'artesian' in line:
coord_start = i + 1
break
# Check if the coordinate type line was found
if coord_start is None:
raise ValueError('Coordinate type not found in POSCAR file')
# Replace the coordinate lines with the new cartesian coordinates
for i, coord in enumerate(cart_coords):
# Split the original coordinate line into fields
fields = lines[coord_start + i].split()
# Replace the first three fields with the new cartesian coordinates
fields[:3] = [' {:19.16f}'.format(x) for x in coord]
# Join the fields back into a line and add a newline character
lines[coord_start + i] = ' '.join(fields) + '\n'
# Replace the coordinate type with 'Cartesian'
lines[coord_start - 1] = 'Cartesian\n'
# Write the new POSCAR file
with open(filename + '2', 'w') as f:
f.writelines(lines)
def compile_elements(n,t1,t2,slab):
c = slab.lattice.matrix[2][2]
shift = get_slab_regions(slab, blength=3.5)[0][0]
thickness = c*(get_slab_regions(slab, blength=3.5)[0][1]-get_slab_regions(slab, blength=3.5)[0][0])
vacuum = c*(1-get_slab_regions(slab, blength=3.5)[0][1])
asf = AdsorbateSiteFinder(slab)
ads_sites = asf.find_adsorption_sites()
frac_ads_sites = {}
for site_type, cart_coords in ads_sites.items():
frac_coords = [slab.lattice.get_fractional_coords(site) for site in cart_coords]
frac_ads_sites[site_type] = frac_coords
inverse_slab = Structure.from_sites(slab)
theta = np.radians(180)
rotation_matrix = np.array([[1, 0, 0],
[0, np.cos(theta), -np.sin(theta)],
[0, np.sin(theta), np.cos(theta)]])
for site in inverse_slab:
rotated_coords = np.dot(rotation_matrix, site.coords)
site.coords = rotated_coords
all_indices = [i for i, site in enumerate(inverse_slab)]
inverse_slab.translate_sites(all_indices, [0, 0, -(vacuum/c)])
inverse_asf = AdsorbateSiteFinder(inverse_slab)
inverse_ads_sites = inverse_asf.find_adsorption_sites()
inverse_frac_ads_sites = {}
for site_type, cart_coords in inverse_ads_sites.items():
inverse_frac_coords = [inverse_slab.lattice.get_fractional_coords(inverse_site) for inverse_site in cart_coords]
inverse_frac_ads_sites[site_type] = inverse_frac_coords
# shift slab to z = 0
all_indices = [i for i, site in enumerate(slab)]
slab.translate_sites(all_indices, [0, 0, -shift])
# change vacuum size to script input
# new_lattice_matrix = slab.lattice.matrix.copy()
# new_lattice_matrix[2][2] = get_slab_regions(slab, blength=3.5)[0][1]*c + 15
# slab.lattice = Lattice(new_lattice_matrix)
data = str(slab).strip().split('\n')[9:]
elements = defaultdict(list)
for line in data:
parts = line.split()
key = float(parts[4])
element = parts[1]
elements[key].append(element)
elements = sorted(elements.items(), reverse=True)
layers = len(elements)
composition_ref = re.findall(r'\d+', structure_oxi.composition.formula)
composition_slab = re.findall(r'\d+', slab.composition.formula)
composition = ''.join(str(slab.composition.formula).split(' ')) + ", " + ':'.join(''.join(match) for match in re.findall(r'(\D+)(\d?+)', slab.composition.reduced_formula))
# print(slab.get_surface_sites())
compiled_list = [f"Slab {n+1:02d} {str(('symmetric' if slab.is_symmetric() else 'non-symmetric') + ', ' + ('stoichiometric' if [float(x)%float(y) for x, y in zip(composition_slab, composition_ref)] == [0]*len(composition_ref) else 'non-stoichiometric')).rjust(32)}"]
compiled_list.append("----------------------------------------")
compiled_list.append(f"compos: {composition.rjust(31)}")
compiled_list.append(f"dipole: {str(slab.dipole[2]).rjust(31)}\n")
compiled_list.append(f"scale : 1.0 1.0 {format(layers/layers_ref, '.1f').rjust(9)}")
compiled_list.append(f"A/t/v : {format(slab.surface_area, '.6f').rjust(9)} {format(thickness, '.6f').rjust(9)} {format(vacuum, '.6f').rjust(9)}")
compiled_list.append('\n'.join(str(slab).strip().split('\n')[6:8])+'\n')
compiled_list.append("----------------------------------------")
compiled_list.append("termin: height: atom configuration:\n")
for key, values in elements:
if key == elements[0][0]:
termination = "UP" if t2 != t1 else "SY"
elif key == elements[-1][0]:
termination = "DN" if t2 != t1 else "SY"
else:
termination = "..".rjust(3)
compiled_list.append(f" {termination.rjust(3)} {format(key, '.6f').rjust(9)} {' / '.join(values)}")
compiled_list.append("")
compiled_list.append("----------------------------------------")
compiled_list.append(f"{'UP' if t2 != t1 else 'SY'} ads: {str(len(frac_ads_sites['ontop'])).rjust(2)} on-top {str(len(frac_ads_sites['bridge'])).rjust(2)} bridge {str(len(frac_ads_sites['hollow'])).rjust(2)} hollow\n")
for pos in ['ontop', 'bridge', 'hollow']:
for site in frac_ads_sites[pos]:
compiled_list.append(f" {pos if pos != 'ontop' else 'on-top'} {format(site[0], '.6f').rjust(9)} {format(site[1], '.6f').rjust(9)} {format(site[2], '.6f').rjust(9)}")
compiled_list.append("")
if t1 != t2:
compiled_list.append(f"DN ads: {str(len(inverse_frac_ads_sites['ontop'])).rjust(2)} on-top {str(len(inverse_frac_ads_sites['bridge'])).rjust(2)} bridge {str(len(inverse_frac_ads_sites['hollow'])).rjust(2)} hollow\n")
for pos in ['ontop', 'bridge', 'hollow']:
for site in inverse_frac_ads_sites[pos]:
compiled_list.append(f" {pos if pos != 'ontop' else 'on-top'} {format(site[0], '.6f').rjust(9)} {format(site[1], '.6f').rjust(9)} {format(site[2], '.6f').rjust(9)}")
compiled_list.append("\n########################################\n")
if n + 1 == input_output or input_output == 0:
poscar = Poscar(slab, sort_structure=True)
poscar.write_file(str(''.join(map(str, input_miller_indices))) + ('SYM' if input_symmetry == True else '') + 'slab' + str(n+1).zfill(2) + ('up' if t2 != t1 else 'sy') + '.vasp')
fig = plt.figure(); ax = fig.add_subplot(111); plot_slab(slab, ax, adsorption_sites=True); ax.set_xlim(min(slab.lattice.matrix[0][0],slab.lattice.matrix[1][0])-2, max(slab.lattice.matrix[0][0],slab.lattice.matrix[1][0])+2); ax.set_ylim(min(slab.lattice.matrix[0][1],slab.lattice.matrix[1][1])-2, max(slab.lattice.matrix[0][1],slab.lattice.matrix[1][1])+2); plt.savefig(str(''.join(map(str, input_miller_indices))) + ('SYM' if input_symmetry == True else '') + 'slab' + str(n+1).zfill(2) + ('up' if t2 != t1 else 'sy') + '.png')
if t1 != t2:
poscar = Poscar(inverse_slab, sort_structure=True)
poscar.write_file(str(''.join(map(str, input_miller_indices))) + ('SYM' if input_symmetry == True else '') + 'slab' + str(n+1).zfill(2) + 'dn' + '.vasp')
fig = plt.figure(); ax = fig.add_subplot(111); plot_slab(inverse_slab, ax, adsorption_sites=True); ax.set_xlim(min(slab.lattice.matrix[0][0],slab.lattice.matrix[1][0])-2, max(slab.lattice.matrix[0][0],slab.lattice.matrix[1][0])+2); ax.set_ylim(min(slab.lattice.matrix[0][1],slab.lattice.matrix[1][1])-2, max(slab.lattice.matrix[0][1],slab.lattice.matrix[1][1])+2); plt.savefig(str(''.join(map(str, input_miller_indices))) + ('SYM' if input_symmetry == True else '') + 'slab' + str(n+1).zfill(2) + 'dn' + '.png')
# convert_poscar_coords('POSCAR.slab' + str(n+1).zfill(2))
return compiled_list
args = sys.argv[1:] if len(sys.argv) > 1 else []
args_dict = {}
for arg in args:
name, value = arg.split('=')
args_dict[name] = value
# usage: python slabs.py input=CONTCAR miller=100 symmetry=False thickness=10 vacuum=15 output=0
input_structure = args_dict['input'] if 'input' in args_dict else "CONTCAR"
input_miller_indices = (int(args_dict['miller'][0]), int(args_dict['miller'][1]), int(args_dict['miller'][2])) if 'miller' in args_dict else (1,0,0)
input_symmetry = eval(args_dict['symmetry']) if 'symmetry' in args_dict else False
input_slab_size = int(args_dict['thickness']) if 'thickness' in args_dict else 10 if input_symmetry == False else 10
input_vacuum_size = int(args_dict['vacuum']) if 'vacuum' in args_dict else 15
input_output = int(args_dict['output']) if 'output' in args_dict else 0
structure = Poscar.from_file(input_structure).structure
oxi_transformation = AutoOxiStateDecorationTransformation()
structure_oxi = oxi_transformation.apply_transformation(structure)
# Use the SlabGenerator class to generate the slab
slabgen = SlabGenerator(structure_oxi, input_miller_indices, min_slab_size=input_slab_size, min_vacuum_size=input_vacuum_size, center_slab=True)
slabs = slabgen.get_slabs(ftol=1e-12, symmetrize=input_symmetry)
slabgen_ref = SlabGenerator(structure_oxi, input_miller_indices, min_slab_size=1, min_vacuum_size=0)
slabs_ref = slabgen_ref.get_slabs(ftol=1e-12)
sites_ref = list(slabs_ref[0].sites)
layers_ref = len([*set([round(site.c,6) for site in sites_ref])])
full_compiled_list = []
ti = 1
for n, slab in enumerate(slabs):
slab = slab.get_orthogonal_c_slab()
if slab.is_symmetric() == True:
tf = ti
else:
tf = ti + 1
# slab_dict[f"Slab {n+1:02d}"] = [("T"+str(t1),"T"+str(t2)),compile_elements(n,t1,t2,slab)]
full_compiled_list.append(compile_elements(n,ti,tf,slab))
ti = tf + 1
with open(str(''.join(map(str, input_miller_indices))) + ('SYM' if input_symmetry == True else '') + '.out', 'w', encoding='utf-8') as f:
print(header, file=f)
print(f'\ninput:\n', file=f)
print(f' bulk structure: {"".join(str(structure.composition.formula).split(" "))}', file=f)
print(f' miller indice: {input_miller_indices}', file=f)
print(f' min slab thickness: {input_slab_size}', file=f)
print(f' min vacuum size: {input_vacuum_size}', file=f)
print(f' symmetric: {input_symmetry}', file=f)
print(f'\nsummary:\n', file=f)
print(f' slabs: {len(slabs)}', file=f)
print(f' terminations: {tf}', file=f)
print(f'\n########################################', file=f)
for compiled_list in full_compiled_list:
for entry in compiled_list:
print(entry, file=f)