tools4vasp 0.0.2__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.
- tools4vasp/__init__.py +0 -0
- tools4vasp/add-MODECAR.py +13 -0
- tools4vasp/chgcar2cube.py +111 -0
- tools4vasp/elf2cube.py +112 -0
- tools4vasp/freq2jmol.py +16 -0
- tools4vasp/freq2mode.py +113 -0
- tools4vasp/kgrid2kspacing.py +34 -0
- tools4vasp/kspacing2kgrid.py +27 -0
- tools4vasp/mixed_interpolate.py +145 -0
- tools4vasp/neb2movie.py +73 -0
- tools4vasp/plotIRC.py +161 -0
- tools4vasp/plotNEB.py +141 -0
- tools4vasp/poscar2nbands.py +21 -0
- tools4vasp/split_vasp_freq.py +310 -0
- tools4vasp/vasp2traj.py +44 -0
- tools4vasp/vaspGetEF.py +178 -0
- tools4vasp/vaspcheck.py +147 -0
- tools4vasp/viewMode.py +61 -0
- tools4vasp-0.0.2.dist-info/LICENSE +21 -0
- tools4vasp-0.0.2.dist-info/METADATA +63 -0
- tools4vasp-0.0.2.dist-info/RECORD +23 -0
- tools4vasp-0.0.2.dist-info/WHEEL +5 -0
- tools4vasp-0.0.2.dist-info/top_level.txt +1 -0
tools4vasp/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Script to convert CHGCAR files to cube files and convert to e-/Ang^3
|
|
4
|
+
# by Patrick Melix
|
|
5
|
+
# 2022/04/04
|
|
6
|
+
#
|
|
7
|
+
# You can import the module and then call .main() or use it as a script
|
|
8
|
+
from pymatgen.io.vasp.outputs import Chgcar
|
|
9
|
+
from pymatgen.io.ase import AseAtomsAdaptor
|
|
10
|
+
from ase.io.cube import write_cube
|
|
11
|
+
import numpy as np
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(inFiles, outFiles, verbose=True, return_integrals=False, return_spin_integrals=False, mult_volume=False):
|
|
16
|
+
assert len(inFiles) == len(outFiles), "Number of input and output files must be equal!"
|
|
17
|
+
integrals = []
|
|
18
|
+
spin_integrals = []
|
|
19
|
+
for iFile,inFile in enumerate(inFiles):
|
|
20
|
+
if not os.path.isfile(inFile):
|
|
21
|
+
raise ValueError('File {:} does not exist'.format(inFile))
|
|
22
|
+
|
|
23
|
+
#if output exists mv to .bak
|
|
24
|
+
if os.path.isfile(outFiles[iFile]):
|
|
25
|
+
if verbose:
|
|
26
|
+
print('ATTENTION: {:} exists, moving to *.bak'.format(outFiles[iFile]))
|
|
27
|
+
os.rename(outFiles[iFile], outFiles[iFile]+'.bak')
|
|
28
|
+
|
|
29
|
+
if verbose:
|
|
30
|
+
print("Reading {}".format(inFile))
|
|
31
|
+
full_chgcar = Chgcar.from_file(inFile)
|
|
32
|
+
spinpol = 'diff' in full_chgcar.data.keys()
|
|
33
|
+
if return_spin_integrals and not spinpol:
|
|
34
|
+
raise ValueError("File {} is not spinpolarized!".format(inFile))
|
|
35
|
+
shape = full_chgcar.data['total'].shape
|
|
36
|
+
n_data = np.prod(shape)
|
|
37
|
+
|
|
38
|
+
if return_integrals:
|
|
39
|
+
integrals.append(np.sum(np.abs(full_chgcar.data['total'])))
|
|
40
|
+
integrals[-1] /= n_data
|
|
41
|
+
if return_spin_integrals:
|
|
42
|
+
spin_integrals.append(np.sum(np.abs(full_chgcar.data['diff'])))
|
|
43
|
+
spin_integrals[-1] /= n_data
|
|
44
|
+
if verbose:
|
|
45
|
+
print("Shape of data: {}".format(shape))
|
|
46
|
+
print("Total number of datapoints: {}".format(n_data))
|
|
47
|
+
if return_integrals:
|
|
48
|
+
integral = integrals[-1]
|
|
49
|
+
else:
|
|
50
|
+
integral = np.sum(np.abs(full_chgcar.data['total']))
|
|
51
|
+
integral /= n_data
|
|
52
|
+
print("Integral of total data is {}".format(integral))
|
|
53
|
+
if spinpol:
|
|
54
|
+
if return_spin_integrals:
|
|
55
|
+
spin_integral = spin_integrals[-1]
|
|
56
|
+
else:
|
|
57
|
+
spin_integral = np.sum(np.abs(full_chgcar.data['diff']))
|
|
58
|
+
spin_integral /= n_data
|
|
59
|
+
print("Integral of diff data is {}".format(spin_integral))
|
|
60
|
+
|
|
61
|
+
origin = np.zeros(3)
|
|
62
|
+
atoms = AseAtomsAdaptor.get_atoms(full_chgcar.structure)
|
|
63
|
+
|
|
64
|
+
#Contrary to VASP Wiki, the CHGCAR is not rho*V, but rho*n_data.
|
|
65
|
+
#So in order to have the integral over space = nelectrons, we need to divide by n_data.
|
|
66
|
+
#Since this would result in super small numbers, we can transform to rho*V
|
|
67
|
+
factor = n_data
|
|
68
|
+
if mult_volume:
|
|
69
|
+
factor /= atoms.get_volume()
|
|
70
|
+
full_chgcar.data['total'] /= factor
|
|
71
|
+
if spinpol:
|
|
72
|
+
full_chgcar.data['diff'] /= factor
|
|
73
|
+
#write cube
|
|
74
|
+
filename = "{}.cube".format(outFiles[iFile])
|
|
75
|
+
if verbose:
|
|
76
|
+
print("Writing {}".format(filename))
|
|
77
|
+
with open(filename, 'w') as f:
|
|
78
|
+
write_cube(f, atoms, data=full_chgcar.data['total'], origin=origin)
|
|
79
|
+
if spinpol:
|
|
80
|
+
filename = "{}_mag.cube".format(outFiles[iFile])
|
|
81
|
+
if verbose:
|
|
82
|
+
print("Writing {}".format(filename))
|
|
83
|
+
with open(filename, 'w') as f:
|
|
84
|
+
write_cube(f, atoms, data=full_chgcar.data['diff'], origin=origin)
|
|
85
|
+
|
|
86
|
+
if return_integrals:
|
|
87
|
+
if len(integrals) == 1:
|
|
88
|
+
if return_spin_integrals:
|
|
89
|
+
return integrals[0], spin_integrals[0]
|
|
90
|
+
else:
|
|
91
|
+
return integrals[0]
|
|
92
|
+
else:
|
|
93
|
+
if return_spin_integrals:
|
|
94
|
+
return integrals, spin_integrals
|
|
95
|
+
else:
|
|
96
|
+
return integrals
|
|
97
|
+
else:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
import argparse
|
|
104
|
+
parser = argparse.ArgumentParser(description='Convert one or many CHGCAR-like files to cube format.')
|
|
105
|
+
parser.add_argument('input', type=str, nargs='+', help='Input Files')
|
|
106
|
+
parser.add_argument('-output', type=str, nargs='+', help='Output File Names (no extension)')
|
|
107
|
+
parser.add_argument('-v', help='Verbose', action='store_true')
|
|
108
|
+
parser.add_argument('--integral', help='Print Integrals', action='store_true')
|
|
109
|
+
parser.add_argument('--volume', help='Multiply the Density with the Cell Volume', action='store_true')
|
|
110
|
+
args = parser.parse_args()
|
|
111
|
+
main(args.input, args.output, verbose=args.v, return_integrals=args.integral, mult_volume=args.volume)
|
tools4vasp/elf2cube.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Script to convert ELFCAR files to cube files
|
|
4
|
+
# by Patrick Melix
|
|
5
|
+
# 2022/04/04
|
|
6
|
+
#
|
|
7
|
+
# You can import the module and then call .main() or use it as a script
|
|
8
|
+
from pymatgen.io.vasp.outputs import Elfcar
|
|
9
|
+
from pymatgen.io.ase import AseAtomsAdaptor
|
|
10
|
+
from ase.io.cube import write_cube
|
|
11
|
+
import numpy as np
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(inFiles, outFiles, verbose=True, return_integrals=False, return_spin_integrals=False):
|
|
16
|
+
assert len(inFiles) == len(outFiles), "Number of input and output files must be equal!"
|
|
17
|
+
integrals = []
|
|
18
|
+
spin_integrals = []
|
|
19
|
+
for iFile, inFile in enumerate(inFiles):
|
|
20
|
+
if not os.path.isfile(inFile):
|
|
21
|
+
raise ValueError('File {:} does not exist'.format(inFile))
|
|
22
|
+
|
|
23
|
+
# if output exists mv to .bak
|
|
24
|
+
if os.path.isfile(outFiles[iFile]):
|
|
25
|
+
if verbose:
|
|
26
|
+
print('ATTENTION: {:} exists, moving to *.bak'.format(outFiles[iFile]))
|
|
27
|
+
os.rename(outFiles[iFile], outFiles[iFile]+'.bak')
|
|
28
|
+
|
|
29
|
+
if verbose:
|
|
30
|
+
print("Reading {}".format(inFile))
|
|
31
|
+
full_elfcar = Elfcar.from_file(inFile)
|
|
32
|
+
spinpol = 'diff' in full_elfcar.data.keys()
|
|
33
|
+
#pymatgen: “total” key refers to Spin.up, and “diff” refers to Spin.down.
|
|
34
|
+
if return_spin_integrals and not spinpol:
|
|
35
|
+
raise ValueError("File {} is not spinpolarized!".format(inFile))
|
|
36
|
+
shape = full_elfcar.data['total'].shape
|
|
37
|
+
n_data = np.prod(shape)
|
|
38
|
+
|
|
39
|
+
if spinpol:
|
|
40
|
+
full_data = full_elfcar.data['total'] + full_elfcar.data['diff']
|
|
41
|
+
else:
|
|
42
|
+
full_data = full_elfcar.data['total']
|
|
43
|
+
|
|
44
|
+
if return_integrals:
|
|
45
|
+
integrals.append(np.sum(np.abs(full_data)))
|
|
46
|
+
if return_spin_integrals:
|
|
47
|
+
spin_integrals.append((np.sum(np.abs(full_elfcar.data['total'])), np.sum(np.abs(full_elfcar.data['diff']))))
|
|
48
|
+
if verbose:
|
|
49
|
+
print("Shape of data: {}".format(shape))
|
|
50
|
+
print("Total number of datapoints: {}".format(n_data))
|
|
51
|
+
if return_integrals:
|
|
52
|
+
integral = integrals[-1]
|
|
53
|
+
else:
|
|
54
|
+
integral = np.sum(np.abs(full_data))
|
|
55
|
+
print("Integral of total data is {}".format(integral))
|
|
56
|
+
if spinpol:
|
|
57
|
+
if return_spin_integrals:
|
|
58
|
+
spin_integral = spin_integrals[-1]
|
|
59
|
+
else:
|
|
60
|
+
spin_integral = (np.sum(np.abs(full_elfcar.data['total'])), np.sum(np.abs(full_elfcar.data['diff'])))
|
|
61
|
+
print("Integral of spin data is up: {}, down: {}".format(*spin_integral))
|
|
62
|
+
|
|
63
|
+
origin = np.zeros(3)
|
|
64
|
+
atoms = AseAtomsAdaptor.get_atoms(full_elfcar.structure)
|
|
65
|
+
|
|
66
|
+
# write cubes
|
|
67
|
+
if spinpol:
|
|
68
|
+
filename = "{}_up.cube".format(outFiles[iFile])
|
|
69
|
+
if verbose:
|
|
70
|
+
print("Writing {}".format(filename))
|
|
71
|
+
with open(filename, 'w') as f:
|
|
72
|
+
write_cube(f, atoms, data=full_elfcar.data['total'], origin=origin)
|
|
73
|
+
filename = "{}_down.cube".format(outFiles[iFile])
|
|
74
|
+
if verbose:
|
|
75
|
+
print("Writing {}".format(filename))
|
|
76
|
+
with open(filename, 'w') as f:
|
|
77
|
+
write_cube(f, atoms, data=full_elfcar.data['diff'], origin=origin)
|
|
78
|
+
filename = "{}_diff.cube".format(outFiles[iFile])
|
|
79
|
+
if verbose:
|
|
80
|
+
print("Writing {}".format(filename))
|
|
81
|
+
with open(filename, 'w') as f:
|
|
82
|
+
write_cube(f, atoms, data=full_elfcar.data['total']-full_elfcar.data['diff'], origin=origin)
|
|
83
|
+
else:
|
|
84
|
+
filename = "{}.cube".format(outFiles[iFile])
|
|
85
|
+
if verbose:
|
|
86
|
+
print("Writing {}".format(filename))
|
|
87
|
+
with open(filename, 'w') as f:
|
|
88
|
+
write_cube(f, atoms, data=full_data, origin=origin)
|
|
89
|
+
|
|
90
|
+
if return_integrals:
|
|
91
|
+
if len(integrals) == 1:
|
|
92
|
+
if return_spin_integrals:
|
|
93
|
+
return integrals[0], spin_integrals[0]
|
|
94
|
+
else:
|
|
95
|
+
return integrals[0]
|
|
96
|
+
else:
|
|
97
|
+
if return_spin_integrals:
|
|
98
|
+
return integrals, spin_integrals
|
|
99
|
+
else:
|
|
100
|
+
return integrals
|
|
101
|
+
else:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
import argparse
|
|
107
|
+
parser = argparse.ArgumentParser(description='Convert one or many ELFCAR files to cube format.')
|
|
108
|
+
parser.add_argument('input', type=str, nargs='+', help='Input Files')
|
|
109
|
+
parser.add_argument('-output', type=str, nargs='+', help='Output File Names (no extension)')
|
|
110
|
+
parser.add_argument('-v', help='Verbose', action='store_true')
|
|
111
|
+
args = parser.parse_args()
|
|
112
|
+
main(args.input, args.output, verbose=args.v)
|
tools4vasp/freq2jmol.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from ase.calculators.vasp import Vasp
|
|
3
|
+
import os
|
|
4
|
+
os.environ['VASP_PP_PATH'] = "/home/patrickm/lib/vasp/ase"
|
|
5
|
+
|
|
6
|
+
calc = Vasp(restart=True, directory='./')
|
|
7
|
+
vibs = calc.get_vibrations()
|
|
8
|
+
vibs.write_jmol()
|
|
9
|
+
|
|
10
|
+
energies = vibs.get_energies()
|
|
11
|
+
nVibs = len(energies)
|
|
12
|
+
for i in range(nVibs):
|
|
13
|
+
with open("vib-{:03d}.xyz".format(i+1), 'w') as f:
|
|
14
|
+
for frame in vibs.iter_animated_mode(i):
|
|
15
|
+
frame.write(f, format='extxyz')
|
|
16
|
+
print("Frequency #{:03d}: {:10.6f} cm-1".format(i+1, energies[i]))
|
tools4vasp/freq2mode.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import math
|
|
4
|
+
from ase.data import atomic_masses
|
|
5
|
+
import ase.io
|
|
6
|
+
from ase.units import _amu as amu, _me as me
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_atomic_mass(element_symbol):
|
|
10
|
+
mass_in_amu = atomic_masses[element_symbol]
|
|
11
|
+
# Convert mass to atomic units (1 amu = 1.66053904e-27 kg)
|
|
12
|
+
mass_in_au = mass_in_amu * amu / me
|
|
13
|
+
return mass_in_au
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def getAtomsFromOutcar(outcar_file):
|
|
17
|
+
atoms = ase.io.read(outcar_file)
|
|
18
|
+
return atoms
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_frequencies(outcar):
|
|
22
|
+
freqLines = []
|
|
23
|
+
for line in outcar:
|
|
24
|
+
if " f/i=" in line:
|
|
25
|
+
freqLines.append(line)
|
|
26
|
+
if "Eigenvectors after division by SQRT(mass)" in line:
|
|
27
|
+
break
|
|
28
|
+
return freqLines
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def generate_mw(frequency, atoms):
|
|
32
|
+
weights = atoms.get_masses()
|
|
33
|
+
freq_mw = []
|
|
34
|
+
for i in range(len(frequency)):
|
|
35
|
+
freq_line = frequency[i]
|
|
36
|
+
factor = math.sqrt(weights[i])
|
|
37
|
+
new_freq_line = []
|
|
38
|
+
for freq in freq_line:
|
|
39
|
+
if not freq == 0:
|
|
40
|
+
new_freq_line.append(freq / factor)
|
|
41
|
+
else:
|
|
42
|
+
new_freq_line.append(0.0)
|
|
43
|
+
freq_mw.append(new_freq_line)
|
|
44
|
+
return freq_mw
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def write_modecar(frequency, filename):
|
|
48
|
+
filestring = ""
|
|
49
|
+
for freq_line in frequency:
|
|
50
|
+
line = ""
|
|
51
|
+
for freq in freq_line:
|
|
52
|
+
if freq >= 0:
|
|
53
|
+
line += " "
|
|
54
|
+
else:
|
|
55
|
+
line += " "
|
|
56
|
+
line += "{:.10E}".format(freq)
|
|
57
|
+
filestring += line + "\n"
|
|
58
|
+
with open(filename, "w") as file:
|
|
59
|
+
file.write(filestring)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def read_frequency_from_outcar(outcar, freq_line, atoms):
|
|
63
|
+
current_frequency = []
|
|
64
|
+
start_index = outcar.index(freq_line) + 2
|
|
65
|
+
end_index = start_index + 0 + len(atoms)
|
|
66
|
+
for line in outcar[start_index:end_index]:
|
|
67
|
+
vals = line.split()
|
|
68
|
+
current_frequency.append([float(vals[3]), float(vals[4]), float(vals[5])])
|
|
69
|
+
return current_frequency
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
parser = argparse.ArgumentParser(
|
|
74
|
+
prog="freq2mode",
|
|
75
|
+
description="A VASP tool, which generates MODECAR and mass-weighted MODCAR files from frequency calculations",
|
|
76
|
+
epilog=""
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument("-o", "--outcar", type=str, help="Optional: specify OUTCAR file", required=False, default="OUTCAR")
|
|
79
|
+
parser.add_argument("-i", "--index", type=int, help="Force non interactive mode by specifying the index of the imaginary frequency to use", required=False, default=-1)
|
|
80
|
+
args = parser.parse_args()
|
|
81
|
+
outcar_file = args.outcar
|
|
82
|
+
with open(outcar_file, "r") as f:
|
|
83
|
+
outcar = f.readlines()
|
|
84
|
+
atoms = getAtomsFromOutcar(outcar_file)
|
|
85
|
+
freqs = get_frequencies(outcar)
|
|
86
|
+
if len(freqs) == 0:
|
|
87
|
+
print("No imaginary frequencies found")
|
|
88
|
+
exit(0)
|
|
89
|
+
if len(freqs) < args.index + 1:
|
|
90
|
+
print("The specified mode was not found in the OUTCAR file")
|
|
91
|
+
exit(1)
|
|
92
|
+
if len(freqs) > 1:
|
|
93
|
+
print(f"Found {len(freqs)} imaginary frequencies. Select the frequency for MODECAR generation")
|
|
94
|
+
freq_index = 0
|
|
95
|
+
for freq in freqs:
|
|
96
|
+
print(f"{freq_index} {freq}")
|
|
97
|
+
freq_index += 1
|
|
98
|
+
if args.index == -1:
|
|
99
|
+
freq_index = int(input("Frequency for MODECAR generation:"))
|
|
100
|
+
else:
|
|
101
|
+
print(f"Using Frequency {args.index}")
|
|
102
|
+
freq_index = args.index
|
|
103
|
+
else:
|
|
104
|
+
print("Found one imaginary mode:")
|
|
105
|
+
print(freqs[0])
|
|
106
|
+
print("Using this mode for MODECAR generation")
|
|
107
|
+
freq_index = 0
|
|
108
|
+
frequency = read_frequency_from_outcar(outcar, freqs[freq_index], atoms)
|
|
109
|
+
print("Writing MODECAR file")
|
|
110
|
+
write_modecar(frequency, "MODECAR")
|
|
111
|
+
print("Writing mass-weighted MODECAR file")
|
|
112
|
+
frequency_mw = generate_mw(frequency, atoms)
|
|
113
|
+
write_modecar(frequency_mw, "MODECAR.MW")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Script to get a KSPACING from a KPOINTS file and a POSCAR
|
|
4
|
+
# by Patrick Melix
|
|
5
|
+
# 2023/05/22
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_kspacing():
|
|
10
|
+
from ase import io
|
|
11
|
+
import numpy as np
|
|
12
|
+
mol = io.read('POSCAR')
|
|
13
|
+
cellparams = mol.cell.cellpar()
|
|
14
|
+
r_cell = mol.cell.reciprocal()
|
|
15
|
+
print("The cell parameters are |a|={} |b|={} |c|={} alpha={} beta={} gamma={}".format(*[ round(x,2) for x in cellparams]))
|
|
16
|
+
print("The reciprocal cell is: {}".format(r_cell))
|
|
17
|
+
with open('KPOINTS', 'r') as f:
|
|
18
|
+
kpoints = f.readlines()
|
|
19
|
+
if kpoints[1].strip() != "0":
|
|
20
|
+
raise ValueError("KPOINTS file does not use Automatic Scheme, but only this is supported!")
|
|
21
|
+
kgrid = [ int(k) for k in kpoints[3].split() ]
|
|
22
|
+
assert len(kgrid) == 3, "Expected to find 3 integers in line 4 of KPOINTS file!"
|
|
23
|
+
print("The KGRID from KPOINTS is: {} {} {}".format(*kgrid))
|
|
24
|
+
kspacing = [ np.linalg.norm(r_cell[i])*2*np.pi/kgrid[i] for i in range(3) ]
|
|
25
|
+
#kgrid = [ int(max(1, np.ceil(np.linalg.norm(r_cell[i])*2*np.pi/kspacing))) for i in range(3)]
|
|
26
|
+
print("The corresponding KSPACING for KGRID {} {} {} is {}Å^-1, {}Å^-1, {}Å^-1".format(*kgrid, *[ round(k, 3) for k in kspacing ]))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
import argparse
|
|
31
|
+
parser = argparse.ArgumentParser(
|
|
32
|
+
description='Get a KSPACING from current POSCAR and KPOINTS')
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
get_kspacing()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Script to get a KGrid from a KSPACING value and a POSCAR
|
|
4
|
+
# by Patrick Melix
|
|
5
|
+
# 2023/05/22
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_kgrid(kspacing):
|
|
10
|
+
from ase import io
|
|
11
|
+
import numpy as np
|
|
12
|
+
mol = io.read('POSCAR')
|
|
13
|
+
cellparams = mol.cell.cellpar()
|
|
14
|
+
r_cell = mol.cell.reciprocal()
|
|
15
|
+
print("The cell parameters are |a|={} |b|={} |c|={} alpha={} beta={} gamma={}".format(*[ round(x,2) for x in cellparams]))
|
|
16
|
+
print("The reciprocal cell is: {}".format(r_cell))
|
|
17
|
+
kgrid = [ int(max(1, np.ceil(np.linalg.norm(r_cell[i])*2*np.pi/kspacing))) for i in range(3)]
|
|
18
|
+
print("The corresponding KGRID for KSPACING {}Å^-1 is: {} {} {}".format(kspacing, *kgrid))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
import argparse
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
description='Get a KGrid from current POSCAR and a KSPACING value')
|
|
25
|
+
parser.add_argument('kspacing', type=float, help='KSPACING value')
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
get_kgrid(args.kspacing)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
# Uses geodesic interpolation for the molecule and idpp interpolation for the surface of a molecule
|
|
4
|
+
|
|
5
|
+
from ase.io import read
|
|
6
|
+
from ase import Atoms
|
|
7
|
+
from ase.mep import NEB
|
|
8
|
+
# from ase.calculators.lj import LennardJones as LJ
|
|
9
|
+
from ase.io import Trajectory
|
|
10
|
+
from ase.visualize import view
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from geodesic_interpolate.interpolation import redistribute
|
|
13
|
+
from geodesic_interpolate.geodesic import Geodesic
|
|
14
|
+
from math import floor,ceil
|
|
15
|
+
|
|
16
|
+
######## Functions as copied from geodesic_wrapper.py ########
|
|
17
|
+
def ase_geodesic_interpolate(initial_mol,final_mol, n_images = 20, friction = 0.01, dist_cutoff = 3, scaling = 1.7, sweep = None, tol = 0.002, maxiter = 15, microiter = 20):
|
|
18
|
+
atom_string = initial_mol.symbols
|
|
19
|
+
|
|
20
|
+
atoms = list(atom_string)
|
|
21
|
+
|
|
22
|
+
initial_pos = [initial_mol.positions]
|
|
23
|
+
final_pos = [final_mol.positions]
|
|
24
|
+
|
|
25
|
+
total_pos = initial_pos + final_pos
|
|
26
|
+
|
|
27
|
+
# First redistribute number of images. Perform interpolation if too few and subsampling if too many
|
|
28
|
+
# images are given
|
|
29
|
+
raw = redistribute(atoms, total_pos, n_images, tol=tol * 5)
|
|
30
|
+
|
|
31
|
+
# Perform smoothing by minimizing distance in Cartesian coordinates with redundant internal metric
|
|
32
|
+
# to find the appropriate geodesic curve on the hyperspace.
|
|
33
|
+
smoother = Geodesic(atoms, raw, scaling, threshold=dist_cutoff, friction=friction)
|
|
34
|
+
|
|
35
|
+
if sweep is None:
|
|
36
|
+
sweep = len(atoms) > 35
|
|
37
|
+
try:
|
|
38
|
+
if sweep:
|
|
39
|
+
smoother.sweep(tol=tol, max_iter=maxiter, micro_iter=microiter)
|
|
40
|
+
else:
|
|
41
|
+
smoother.smooth(tol=tol, max_iter=maxiter)
|
|
42
|
+
finally:
|
|
43
|
+
|
|
44
|
+
all_mols = []
|
|
45
|
+
|
|
46
|
+
for pos in smoother.path:
|
|
47
|
+
mol = Atoms(atom_string, pos)
|
|
48
|
+
all_mols.append(mol)
|
|
49
|
+
|
|
50
|
+
return all_mols
|
|
51
|
+
|
|
52
|
+
########## Functions copied from geodesic_wrapper.py ##########
|
|
53
|
+
def interpolate_traj(initial,final,LEN,method,calculator=None):
|
|
54
|
+
interpolated_length=LEN
|
|
55
|
+
initial_1=initial.copy()
|
|
56
|
+
initial_1.calc=calculator #attach calculator to images between start and end
|
|
57
|
+
# RETURN=initial_1.copy()
|
|
58
|
+
images = [initial]
|
|
59
|
+
images += [initial_1.copy() for i in range(interpolated_length)] #create LEN instances
|
|
60
|
+
images += [final]
|
|
61
|
+
nebts = NEB(images)
|
|
62
|
+
nebts.interpolate(mic=True,method=method,apply_constraint=True)
|
|
63
|
+
new_traj=Trajectory(f'{LEN}_{method}_interpol.traj',mode='w')
|
|
64
|
+
for im in images:
|
|
65
|
+
new_traj.write(im)
|
|
66
|
+
def create_trajs(START,END,LEN,method):
|
|
67
|
+
pass
|
|
68
|
+
# calc = LJ()
|
|
69
|
+
# TRAJ=interpolate_traj(START,END,LEN,method,calculator=calc)
|
|
70
|
+
|
|
71
|
+
def vprint(words):
|
|
72
|
+
if args.verbose:
|
|
73
|
+
print(words)
|
|
74
|
+
|
|
75
|
+
def create_both(initial_mol, final_mol, LEN, method):
|
|
76
|
+
create_trajs(initial_mol, final_mol, LEN, method) #create the interpolated trajectory using the idpp/direct method
|
|
77
|
+
trajectory_pbc=read(f'{LEN}_{method}_interpol.traj',index=':') #read the interpolated trajectory
|
|
78
|
+
vprint(f"Interpolated trajectory created with {len(trajectory_pbc)} images using {method} method")
|
|
79
|
+
trajectory_geodesic = ase_geodesic_interpolate(initial_mol,final_mol, n_images= LEN+1) #create the geodesic interpolated trajectory
|
|
80
|
+
vprint(f"Geodesic interpolated trajectory created with {len(trajectory_geodesic)} images")
|
|
81
|
+
difference=initial_mol[0].position-trajectory_geodesic[0][0].position
|
|
82
|
+
for image in trajectory_geodesic: #fix the the position of the shifted molecule since the geodesic interpolation does not use pbc
|
|
83
|
+
image.set_cell(initial_mol.cell)
|
|
84
|
+
for at in image:
|
|
85
|
+
at.position=at.position+difference
|
|
86
|
+
trajectory_geodesic.append(final_mol)
|
|
87
|
+
return(trajectory_pbc, trajectory_geodesic)
|
|
88
|
+
######### Combination ######### @FThiemann
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
import argparse
|
|
94
|
+
parser = argparse.ArgumentParser(description='Interpolate Mixing the geodesic and idpp/direct interpolation methods.')
|
|
95
|
+
parser.add_argument('start', type=str, help='POSCAR file of initial molecule')
|
|
96
|
+
parser.add_argument('end', type=str, help='POSCAR file of final molecule')
|
|
97
|
+
parser.add_argument('Images', type=int, help='Number of NEB Images to generate')
|
|
98
|
+
parser.add_argument('SurfaceCutoff', type=int, help='Length of the molecule')
|
|
99
|
+
parser.add_argument('method', type=str,choices=['idpp','direct'] , help='Interpolation method')
|
|
100
|
+
parser.add_argument('-c','--check', action='store_true', help='Check the cutoff trajectory')
|
|
101
|
+
parser.add_argument('-s','--show', action='store_true', help='view trajectory')
|
|
102
|
+
parser.add_argument('-v','--verbose', action='store_true', help='verbosity of output')
|
|
103
|
+
parser.add_argument('-i','--intermediate',help='Use a guess for the transition state, will be interpolated from start to I and from I to end')
|
|
104
|
+
global args
|
|
105
|
+
args = parser.parse_args()
|
|
106
|
+
|
|
107
|
+
initial_mol = read(args.start)
|
|
108
|
+
final_mol = read(args.end)
|
|
109
|
+
LEN = args.Images
|
|
110
|
+
moleculeStart = -1 * args.SurfaceCutoff
|
|
111
|
+
method = args.method
|
|
112
|
+
|
|
113
|
+
if args.intermediate:
|
|
114
|
+
vprint(f"using intermediate. first segment: {floor(LEN/2)}, second {ceil(LEN/2)} ")
|
|
115
|
+
in_read = read(args.intermediate)
|
|
116
|
+
trajectory_pbc1, trajectory_geodesic1 = create_both(initial_mol, in_read, floor((LEN)/2)-1, method)
|
|
117
|
+
trajectory_pbc2, trajectory_geodesic2 = create_both(in_read , final_mol, ceil((LEN)/2)-1, method)
|
|
118
|
+
trajectory_pbc = trajectory_pbc1.copy()
|
|
119
|
+
trajectory_pbc += trajectory_pbc2
|
|
120
|
+
trajectory_geodesic = trajectory_geodesic1.copy()
|
|
121
|
+
trajectory_geodesic += trajectory_geodesic2
|
|
122
|
+
vprint(f"total length of geodesic: {len(trajectory_geodesic)}; pbc: {len(trajectory_pbc)}")
|
|
123
|
+
else:
|
|
124
|
+
trajectory_pbc, trajectory_geodesic = create_both(initial_mol, final_mol, LEN, method)
|
|
125
|
+
newTrajectory = trajectory_pbc.copy()
|
|
126
|
+
new_traj=Trajectory(f'{LEN}_interpol.traj',mode='w')
|
|
127
|
+
for nr, image in enumerate(trajectory_pbc):
|
|
128
|
+
atoms_idpp = trajectory_pbc[nr]
|
|
129
|
+
atoms_geodesic = trajectory_geodesic[nr]
|
|
130
|
+
molecule = atoms_geodesic[moleculeStart:]
|
|
131
|
+
surface = atoms_idpp[:moleculeStart]
|
|
132
|
+
if nr == 0 and args.check is True: #check the cutoff
|
|
133
|
+
view(surface)
|
|
134
|
+
view(molecule)
|
|
135
|
+
merged = Atoms(surface + molecule) #merge the molecule and the surface
|
|
136
|
+
newTrajectory[nr] = merged
|
|
137
|
+
new_traj.write(merged)
|
|
138
|
+
N=f'{nr:02d}'
|
|
139
|
+
vprint(f'Writing {N}/POSCAR')
|
|
140
|
+
Path(N).mkdir(parents=True,exist_ok=True)
|
|
141
|
+
newTrajectory[nr].write(f'{N}/POSCAR',format='vasp')
|
|
142
|
+
|
|
143
|
+
if args.show is True:
|
|
144
|
+
view(newTrajectory)
|
|
145
|
+
main()
|
tools4vasp/neb2movie.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Script to convert VASP NEB calculation to ASE-extxyz trajectory
|
|
4
|
+
# by Patrick Melix
|
|
5
|
+
#
|
|
6
|
+
# You can import the module and then call .main() or use it as a script
|
|
7
|
+
from ase import io
|
|
8
|
+
import os
|
|
9
|
+
import glob
|
|
10
|
+
|
|
11
|
+
def main(outFile='movie.xyz', workdir='.', wrap='False', use=None):
|
|
12
|
+
"""
|
|
13
|
+
use: None -> Auto use CONTCAR if available, otherwise POSCAR
|
|
14
|
+
CONTCAR or POSCAR
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
#if output exists mv to .bak
|
|
18
|
+
if os.path.isfile(outFile):
|
|
19
|
+
print('ATTENTION: {:} exists, moving to *.bak'.format(outFile))
|
|
20
|
+
os.rename(outFile, outFile+'.bak')
|
|
21
|
+
|
|
22
|
+
if use:
|
|
23
|
+
filename = use
|
|
24
|
+
else:
|
|
25
|
+
if os.path.isfile(os.path.join(workdir,'01','CONTCAR')):
|
|
26
|
+
filename = 'CONTCAR'
|
|
27
|
+
elif os.path.isfile(os.path.join(workdir,'01','POSCAR')):
|
|
28
|
+
filename = 'POSCAR'
|
|
29
|
+
else:
|
|
30
|
+
raise RuntimeError("Could neither find CONTCAR nor POSCAR in {:}".format(os.path.join(workdir,'01')))
|
|
31
|
+
print("Using {:} files.".format(filename))
|
|
32
|
+
mol = []
|
|
33
|
+
dirs = glob.glob(os.path.join(workdir,'[0-9][0-9]'))
|
|
34
|
+
dirs.sort()
|
|
35
|
+
print("Found {:} NEB subdirs.".format(len(dirs)))
|
|
36
|
+
print("Loading images ", end='')
|
|
37
|
+
for i,image in enumerate(dirs):
|
|
38
|
+
if (i == 0) or (i == len(dirs)-1):
|
|
39
|
+
imagePath = os.path.join(image, 'POSCAR')
|
|
40
|
+
else:
|
|
41
|
+
imagePath = os.path.join(image,filename)
|
|
42
|
+
if not os.path.isfile(imagePath):
|
|
43
|
+
raise RuntimeError('File {:} does not exist'.format(str(imagePath)))
|
|
44
|
+
|
|
45
|
+
print(" {:}".format(os.path.split(image)[-1]), end='')
|
|
46
|
+
mol.append(io.read(imagePath, format='vasp'))
|
|
47
|
+
|
|
48
|
+
print("")
|
|
49
|
+
for frame in mol:
|
|
50
|
+
if wrap:
|
|
51
|
+
frame.wrap(center=(0.0,0.0,0.0))
|
|
52
|
+
frame.write(outFile,append=True)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
import argparse
|
|
59
|
+
parser = argparse.ArgumentParser(description='Convert VASP NEB to ASE-extxyz trajectory')
|
|
60
|
+
parser.add_argument('-o', type=str, help='Output xyz file', default='movie.xyz')
|
|
61
|
+
parser.add_argument('-i', type=str, help='Workdir', default='.')
|
|
62
|
+
parser.add_argument('-w', help='Wrap structure with origin as center', action='store_const', default=False, const=True)
|
|
63
|
+
parser.add_argument('use', type=str, help='Use 1: CONTCAR or 0: POSCAR, default: Auto choose CONTCAR over POSCAR.', nargs='?')
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
if args.use == "1":
|
|
66
|
+
use = 'CONTCAR'
|
|
67
|
+
elif args.use == "0":
|
|
68
|
+
use = 'POSCAR'
|
|
69
|
+
else:
|
|
70
|
+
use = None
|
|
71
|
+
main(args.o, args.i, args.w, use)
|
|
72
|
+
|
|
73
|
+
|