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 ADDED
File without changes
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env python3
2
+ from ase import io
3
+ import numpy as np
4
+
5
+ poscar = io.read('POSCAR')
6
+
7
+ add = np.loadtxt('MODECAR')
8
+
9
+ poscar.write('poscar+modecar.xyz')
10
+
11
+ poscar.positions += add
12
+
13
+ poscar.write('poscar+modecar.xyz', append=True)
@@ -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)
@@ -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]))
@@ -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()
@@ -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
+