├── BUILD ├── CHANGELOG ├── README.md ├── build.sh └── src ├── plot └── __main__.py └── unfolding ├── __main__.py ├── __pycache__ └── utils.cpython-34.pyc ├── errors.py ├── parse.py ├── unfolding.py ├── utils.py └── write.py /BUILD: -------------------------------------------------------------------------------- 1 | BUILD instructions 2 | ================== 3 | 4 | vasp_unfold is actually just a collection of Python 5 | source files. They can be bundled together into an 6 | executable archive by running the build.sh script. 7 | Once vasp_unfold is generated by build sript, it can 8 | be run independently and you can place it wherever 9 | it fits you best to use it. Since version 1.3 this 10 | script also builds plotting utility fatplot. 11 | 12 | If Python is invoked in a non-standard way on your 13 | system, please change the PYTHON_COMMAND variable 14 | in build.sh script. 15 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Jan 23th 2015 2 | ============= 3 | 4 | v1.0 5 | 6 | * Added the possiblity to parse the PROCAR file from the 7 | non-collinear spin-polarized calculation. 8 | 9 | * Added the possibility to unfold both spin up and spin 10 | down components. 11 | 12 | 13 | Jan 28th 2015 14 | ============= 15 | 16 | v1.1 17 | 18 | * Refactored code into separate files. App can be packed 19 | into single executable with pack.sh script. 20 | 21 | * Spin polarized and non-collinear PROCARS are now fully 22 | parsed. 23 | 24 | 25 | Aug 18th 2015 26 | ============= 27 | 28 | v1.2 29 | 30 | * Can handle vacancies and excess atoms not. One to one 31 | mapping check is now optional. 32 | 33 | 34 | Aug 31st 2015 35 | ============= 36 | 37 | v1.3 38 | 39 | * Changed the way orbital weights are unfolded. Orbital 40 | weights from the non-collinear spin-polarized calculation 41 | are now fully unfolded. 42 | 43 | * Added a small plotting utility to plot fatbands. 44 | 45 | * File structure reorganized again. 46 | 47 | * Added extended error message regarding broken PROCAR 48 | formatting. 49 | 50 | 51 | Mar 4th 2016 52 | ============= 53 | 54 | v1.31 55 | 56 | * Fixed the output format for the unfolded PROCAR in the case 57 | of the non-collinear calculation. 58 | 59 | 60 | Apr 25th 2017 61 | ============= 62 | 63 | v1.32 64 | 65 | * Added handling of fractional translation generators components 66 | as general fractions (previously only 1/x was possible). 67 | 68 | May 30th 2017 69 | ============= 70 | 71 | v1.33 72 | 73 | * Fixed improper checking of linear independence of fractional translation 74 | generators. 75 | 76 | 77 | May 31st 2017 78 | ============= 79 | 80 | v1.34 81 | 82 | * Fixed bug with numpy fraction issues. 83 | 84 | 85 | December 12th 2017 86 | ============= 87 | 88 | v1.4 89 | 90 | * Added handling of PROCAR files for VASP 5.4.4 91 | * Fixed problem where POSCARS with Selective Dynamics were not handled -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bandstructure unfolding for VASP 2 | 3 | Here you can download vasp_unfold, a Python script which you can use to unfold the bandstructures obtained with VASP. This code is based on the method discussed in [Ref. 1](#ref_1). 4 | 5 | You can use the code in whatever way you see fit, but if it is used to produce the data for the publication, please cite [Ref. 1](#ref_1). 6 | 7 | In addition, a small utility called fatplot is provided to quickly plot the bandstructures contained in the VASPs PROCAR files. 8 | 9 | The code is given as is, ie. there is no guarantee that it will work. However, if you have some problems, or the code does not behave in a way you expect it to, I encourage you to report the problem to my [e-mail](mailto:tomkeus@gmail.com) and I will try to fix it as soon as possible. 10 | 11 | ## Installation 12 | 13 | Dependencies are 14 | 15 | * [Python](http://www.python.org/downloads/) ofcourse. The script requires Python2.7 or greater 16 | * [NumPy](http://www.scipy.org/scipylib/download.html) library for Python 17 | * [matplotlib](http://matplotlib.org/downloads.html) library for Python (only needed if fatplot is used) 18 | 19 | No special installation is required. Just place it wherever it suits you and run it. 20 | 21 | ## Usage 22 | 23 | vasp_unfold works as a postprocessing tool. The unfolding is achieved through manipulation of orbital weights. Basically, in the unfolded bandstructure, orbital weights of some bands are set to zero. This means that **the effect of unfolding can only be seen with the fatband plots**. In order for vasp_unfold to be able to work, the phase information must be present in the PROCAR file which means that **LORBIT=12 must be set in the INCAR file**. 24 | 25 | The workflow is as follows: once the bandstructure is calculated with VASP, the vasp_unfold is operated on the PROCAR file obtained in the bandstructure calculation, whereby a number of PROCAR files is produced, containing the unfolded bandstructure. Every PROCAR file generated in the output contains projection to one of the irreps as explained in [Ref. 1](#ref_1). 26 | 27 | The command line usage of vasp_unfold is following 28 | 29 | ``` 30 | usage: vasp_unfold [-h] [--tgen SX,SY,SZ] [--out OUT] [--eps EPS] 31 | [--all-irreps] [--check-mapping] 32 | poscar procar 33 | ``` 34 | 35 | where the parameters are 36 | 37 | ``` 38 | --tgen Fractional translation generator 39 | --out Output filename prefix 40 | --eps Numerical tolerance for position discrimination 41 | --all-irreps Write all irreps from the unfolding 42 | --check-mapping Verify if fractional translations map atoms one-to-one 43 | --vasp-version Which version of VASP was used to produce the PROCAR file 44 | poscar Location of POSCAR file 45 | procar Location of PROCAR file 46 | ``` 47 | 48 | Let us say that we have a 2x3x1 supercell. In total we have six unit cells contained within the supercell. To every unit cell in the supercell, corresponds one fractional translation (relative to the supercell), so that we have six fractional translations in total. These six fractional translations can be generated from two fractional translations given by vectors (1/2,0,0) and (0,1/3,0). We can then unfold the supercell bandstructure with 49 | 50 | ``` 51 | vasp_unfold --tgen 1/2,0,0 --tgen 0,1/3,0 POSCAR PROCAR 52 | ``` 53 | 54 | The unfolded bandstructures will be located in PROCAR.irrep.0 file. In case --all-irreps flag was specified, the unfolded bandstructure will be located in PROCAR.irrep.0 through PROCAR.irrep.5 files. 55 | 56 | **NOTE 1**: No whitespace is allowed in the fractional translation generator specification. Also, the components can be either 0, or 1/N, where N is an integer. Floating point values are not allowed. 57 | 58 | **NOTE 2**: Do not enable --check-mapping flag if your structure has vacancies or excess atoms, since in this case fractional translations do not map every atom onto some other atom. 59 | 60 | **NOTE 3**: Numerical tolerance for position discrimination, --eps, is not specified in the units of your unit cell (ie. Angstroms). Instead it is given in the fractional units. 61 | 62 | **NOTE 4**: Unfolding of the k-path is **NOT** automatic! This means, that you have to manually specify the k-path in the Brillouin zone of your supercell. Fortunately, that is easy to do! If you have a n1 x n2 x n3 supercell, you just need to multiply every k-point's 1st, 2nd and 3rd components by n1, n2 and n3 respectively (when the KPOINTS file is in the reciprocal mode). 63 | 64 | ## Resolving the issues with the code 65 | 66 | Here is a little advice pertaining to the "Translations are not one-to-one" error when --chek-mapping flag is enabled. This problem arises because the vasp_unfold script tries to figure out which atoms are mapped onto which atoms under the action of the fractional translations. If the supercell would be perfectly symmetrical under the fractional translations, this issue would not occur. However, in real life, the supercell will usually break this translational symmetry which means that atoms wont be mapped exactly onto each other by the fractional translations. 67 | 68 | The resolution to this problem is quite simple. If increase of the numerical tolerance parameter does not give any result, simply use the POSCAR file where the atomic positions are restored to their symmetryc sites. Practically, if your supercell is a result of the structural optimization, use the initial POSCAR file instead of the final one. 69 | 70 | ## References 71 | 72 | 1. M. Tomić, H. O. Jeschke and R. Valentí - Unfolding of the electronic structure through the induced representations of space groups: Application to Fe-based superconductors, [Phys. Rev. **B** 90, 195121 (2014)](http://journals.aps.org/prb/abstract/10.1103/PhysRevB.90.195121) ([arXiv preprint](http://arxiv.org/abs/1408.2258)) 73 | 74 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This scripts packs the Python source files 4 | # into an executable ZIP archive. This is done 5 | # separatly for the vasp_unfold and 6 | 7 | # Names for the executables 8 | VASP_UNFOLD="vasp_unfold" 9 | PLOT="fatplot" 10 | 11 | # Command used to invoke Python 12 | PYTHON_COMMAND="python" 13 | 14 | # Command used to invoke env 15 | ENV_COMMAND="/usr/bin/env" 16 | 17 | 18 | SRC_FILES="__main__.py parse.py unfolding.py utils.py write.py errors.py" 19 | PLOT_SRC_FILES="__main__.py" 20 | 21 | # Change into source directory 22 | cd src 23 | 24 | # Zip the source files 25 | cd unfolding; zip ../../${VASP_UNFOLD}.zip ${SRC_FILES}; cd ..; 26 | cd plot; zip ../../${PLOT}.zip ${PLOT_SRC_FILES}; cd ..; 27 | 28 | cd .. 29 | 30 | # Prepend shebang to the zip archive 31 | echo "#! ${ENV_COMMAND} ${PYTHON_COMMAND}" | cat - ${VASP_UNFOLD}.zip > ${VASP_UNFOLD} 32 | echo "#! ${ENV_COMMAND} ${PYTHON_COMMAND}" | cat - ${PLOT}.zip > ${PLOT} 33 | 34 | # Remove zip archive 35 | rm ${VASP_UNFOLD}.zip 36 | rm ${PLOT}.zip 37 | 38 | # Make it executable 39 | chmod +x ${VASP_UNFOLD} 40 | chmod +x ${PLOT} 41 | -------------------------------------------------------------------------------- /src/plot/__main__.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold/fatplot 4 | # FILE: __main__.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.32 8 | # DATE: Apr 25th 2017 9 | # 10 | #=========================================================== 11 | 12 | 13 | import os 14 | import numpy as np 15 | import argparse 16 | 17 | import matplotlib 18 | matplotlib.use('Agg') 19 | import matplotlib.pyplot as plot 20 | 21 | # Handle color arguments 22 | def color(string): 23 | try: 24 | # Try to convert to tuple 25 | return eval(string) 26 | except: 27 | # If cannot convert to tuple just 28 | # return the original string 29 | return string 30 | 31 | desc_str = '''Simple program used to quickly plot the orbital weights 32 | of the band structure contained in the specified PROCAR file. 33 | ''' 34 | 35 | parser = argparse.ArgumentParser(prog='fatplot', description = desc_str) 36 | 37 | parser.add_argument('procar', type=str, help='POSCAR file') 38 | parser.add_argument('output', type=str, help='Filename for the plot. It accepts ' 39 | 'all image formats supported by matplotlib.') 40 | 41 | parser.add_argument('--figsize', type=eval, default=(6, 4), 42 | help='Figure size in inches formatted as width,height ' 43 | '(no spaces allowed). Default is 6,4') 44 | parser.add_argument('--dpi', type=float, default=300, 45 | help='Plot resolution in dots per inch. Default is 300.') 46 | parser.add_argument('--marker', type=str, default='o', 47 | help='Marker for the fatband plot. Default is o.') 48 | parser.add_argument('--markersize', type=float, default=20, 49 | help='Marker size. Default is 20.') 50 | parser.add_argument('--color', type=color, default='"steelblue"', 51 | help='Color for the marker. It accepts any color specification ' 52 | 'accepted by matplotlib. Color specified as r,g,b tuple should ' 53 | 'not have any spaces.') 54 | parser.add_argument('--elim', type=eval, default=(1,-1), 55 | help='Energy range for the plot, specified as emin,emax ' 56 | '(no spaces allowed). Default is entire band range.') 57 | parser.add_argument('--efermi', type=float, default=0, 58 | help='Fermi energy. Default is 0.') 59 | parser.add_argument('--pow', type=float, default=1, 60 | help='Raise orbital weights to the specified integer power. ' 61 | 'Powers larger than 1 help to filter out the ghost bands ' 62 | 'in the unfolded band structures.') 63 | 64 | args = parser.parse_args() 65 | 66 | 67 | # Commands to extract the needed information from the PROCAR file 68 | grep_npoints = 'grep -m 1 "of k\-points" {0} | tr -s " " | cut -d" " -f4' 69 | grep_kpoints = 'grep -E "^ k\-point " {0} | tr -s " " | cut -d" " -f5-7' 70 | grep_bands = 'grep -E "^band" {0} | tr -s " " | cut -d" " -f5' 71 | grep_weights = 'grep -E "^tot" {0} | tr -s " " | cut -d" " -f11' 72 | 73 | # Extract the number of k-points 74 | npoints = int(os.popen(grep_npoints.format(args.procar)).read()) 75 | 76 | # Extract the k-points 77 | kpoints = os.popen(grep_kpoints.format(args.procar)) 78 | kpoints = np.fromfile(kpoints, count=3*npoints, dtype=float, sep=' ') 79 | 80 | # Extract the band energies 81 | bands = os.popen(grep_bands.format(args.procar)) 82 | bands = np.fromfile(bands, count=-1, dtype=float, sep=' ') 83 | 84 | # Extract the total orbital weights for each band 85 | weights = os.popen(grep_weights.format(args.procar)) 86 | weights = np.fromfile(weights, count=-1, dtype=float, sep=' ') 87 | 88 | # Figure out the number of bands 89 | nbands = len(bands)/npoints 90 | 91 | # Figure out the number of orbital blocks per band 92 | # 1 for collinear calculation, 4 for non-collinear 93 | # In non-collinear case we just need the first one 94 | wdim = len(weights)/(npoints*nbands) 95 | 96 | # Reshape the arrays into their proper shapes 97 | kpoints = kpoints.reshape((npoints, 3)) 98 | bands = bands.reshape((npoints, nbands))-args.efermi 99 | weights = weights[::wdim].reshape((npoints, nbands)) 100 | 101 | # Raise the weights to the specified power 102 | if args.pow != 1: 103 | weights = np.power(weights, args.pow) 104 | 105 | 106 | # Try to guess where the high symmetry points 107 | # are by looking for the repeated k-points 108 | dk = np.zeros((npoints, 3), float) 109 | 110 | # Displacement between i+1st and ith k-point 111 | dk[1:] = kpoints[1:]-kpoints[:-1] 112 | 113 | # Get the magnitude of displacements 114 | dk = np.sqrt(np.sum(dk*dk, axis=1)) 115 | 116 | # Locate the high-symmetry points 117 | ipoint = np.where(dk < 1e-6)[0] 118 | 119 | # Generate plot's x-axis from the adjacent k-point 120 | # displacement magnitudes 121 | x = np.cumsum(dk) 122 | 123 | # Get the plot's x-coordinates for high-symmetry k-points 124 | xsym = x[ipoint] 125 | 126 | # Figure out the maximum possible energy boundaries 127 | # based on the energy extend of all bands 128 | e_low = np.min(bands)-1.0 129 | e_high = np.max(bands)+1.0 130 | 131 | plot.figure(figsize=args.figsize) 132 | 133 | # Plot horizontal line for the Fermi level 134 | plot.plot(x[[0,-1]], [0, 0], color='gray', zorder=-1) 135 | 136 | # Plot vertical lines for the high-symmetry points 137 | for xi in xsym: 138 | plot.plot([xi, xi], [e_low, e_high], color='gray', zorder=-1) 139 | 140 | # Plot the weights 141 | for bi, wi in zip(bands.T, weights.T): 142 | plot.scatter(x, bi, s=args.markersize*wi, marker=args.marker, 143 | color=args.color, lw=0) 144 | 145 | # Fix x and y-axis boundaries 146 | plot.xlim(x[0], x[-1]) 147 | 148 | if args.elim[0] < args.elim[1]: 149 | plot.ylim(*args.elim) 150 | else: 151 | plot.ylim(e_low, e_high) 152 | 153 | # Set x-axis ticks 154 | plot.xticks(xsym, ['']*len(xsym)) 155 | 156 | # Save the figure 157 | plot.savefig(args.output, dpi=args.dpi, bbox_inches='tight') 158 | 159 | 160 | -------------------------------------------------------------------------------- /src/unfolding/__main__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | 4 | #============================================================================ 5 | # 6 | # PROJECT: vasp_unfold 7 | # FILE: __main__.py 8 | # AUTHOR: Milan Tomic 9 | # EMAIL: tomic@th.physik.uni-frankfurt.de 10 | # VERSION: 1.4 11 | # DATE: December 12th 2017 12 | # 13 | # 14 | # vasp_unfold is a code whose purpose is to perform the unfolding 15 | # of band structures calculated with VASP. It is based on the method 16 | # described in (Phys. Rev. B 90, 195121). If this code have been used to 17 | # obtain the data in your publication please cite the given reference. 18 | # 19 | # This code is provided as is. There is no guarantee that it will work. 20 | # However, if you encounter errors or unexpected behavor, please report 21 | # it to tomic@itp.uni-frankfurt.de and I will try to improve it. 22 | # 23 | #============================================================================ 24 | 25 | 26 | import numpy as np 27 | import argparse 28 | import sys 29 | from utils import post_error, translation, version 30 | from unfolding import build_translations, build_operators, build_projectors 31 | from parse import parse_poscar, parse_procar 32 | from write import write_procar 33 | import errors 34 | 35 | def main(): 36 | desc_str = 'Unfold bands calculated by VASP. For this, phase '\ 37 | 'information needs to be present in the PROCAR file '\ 38 | 'which means that bands need to be calculated with '\ 39 | 'the option LORBIT=12 specified in the INCAR file. '\ 40 | 'The required input consists of POSCAR file, PROCAR '\ 41 | 'file and a set of up to three fractional translations. '\ 42 | 'There is no need to specify all translations, just the '\ 43 | 'generators. The programs will generate all distinct '\ 44 | 'translations from the generators and generate all irreps. '\ 45 | 'NOTE: In cas of non-collinear spin-polarized calculations ' \ 46 | 'mx, my and mz components of orbital weights are not ' \ 47 | 'unfolded, only spin up and spin down totals.' 48 | 49 | 50 | parser = argparse.ArgumentParser(prog='vasp_unfold', description = desc_str) 51 | 52 | parser.add_argument('poscar', type=str, help='POSCAR file') 53 | parser.add_argument('procar', type=str, help='PROCAR file') 54 | 55 | parser.add_argument('--tgen', type=translation, action='append', 56 | metavar='SX,SY,SZ', help='Fractional translation ' 57 | 'generator. No whitespaces are allowed between the ' 58 | 'components! SX, SY and SZ can be either 0 or 1/n, ' 59 | 'where n is an integer. Up to three linearly ' 60 | 'independant generators can be specified.') 61 | 62 | parser.add_argument('--out', type=str, help='Output filename. If left ' 63 | 'unspecified output is writen to PROCAR.irrep.n ' 64 | 'where PROCAR is location of the input PROCAR file. ' 65 | 'If specified, output is written to OUT.irrep.n.') 66 | 67 | parser.add_argument('--eps', type=float, default=1e-6, help='Numerical ' 68 | 'precision. When building permutation representation ' 69 | 'of the fractional translations this parameter is used ' 70 | 'to determine whether two fractional positions are ' 71 | 'identical. For irregular structures, it may need to ' 72 | 'be increased. If this does not help, try to tweak ' 73 | 'the atomic positions in POSCAR to make the structure ' 74 | 'more regular. Default is 1e-6.') 75 | 76 | parser.add_argument('--all-irreps', default=False, action='store_true', 77 | help='Flag specifying that all irreps from the unfolding ' 78 | 'will be written to the output. By default, only irrep 0 ' 79 | '(unit irrep) is written out.') 80 | 81 | parser.add_argument('--check-mapping', action='store_true', default=False, 82 | help='Specifies to check if supplied translation ' 83 | 'generators generate fractional translations which are ' 84 | 'one-to-one, ie. map every atom on exactly one other atom ' 85 | 'in the unit cell. This MUST not be enabled for the cases ' 86 | 'where vacancies or excess atoms are present.') 87 | 88 | parser.add_argument('--vasp-version', type=version, default='5.2.2', help='Version of VASP' 89 | 'that produced the PROCAR file. All versions prior to 5.4.4' 90 | 'use the same format. Since 5.4.4 there is a new format.') 91 | 92 | args = parser.parse_args() 93 | 94 | tgens = args.tgen 95 | 96 | trans, irreps = build_translations(tgens) 97 | 98 | try: 99 | cell, spos, symbols = parse_poscar(args.poscar) 100 | except: 101 | post_error('Unable to parse the input POSCAR file. Please ' 102 | 'check if the file exists and is formatted properly.', True) 103 | 104 | ops = build_operators(spos, trans, args.check_mapping, args.eps) 105 | 106 | try: 107 | data = parse_procar(args.procar, args.vasp_version) 108 | except Exception as exc: 109 | post_error(errors.poscar_parse_error, True) 110 | 111 | if data[-1] is None: 112 | post_error('Phase information has to be present in the PROCAR ' 113 | 'file. Please repeat the calculation with LORBIT=12.', True) 114 | 115 | phases = np.copy(data[-1]) 116 | 117 | norbs = phases.shape[1]/len(spos) 118 | 119 | projs = build_projectors(irreps, ops, norbs) 120 | 121 | if args.out is None: 122 | output = args.procar 123 | else: 124 | output = args.out 125 | 126 | if args.all_irreps: 127 | nirrep = len(projs) 128 | else: 129 | nirrep = 1 130 | 131 | for i, p in enumerate(projs[:nirrep]): 132 | for s in xrange(data[-1].shape[-1]): 133 | try: 134 | data[-1][:,:,:,s] = np.dot(p, phases[:,:,:,s]).swapaxes(0, 1) 135 | except: 136 | post_error('Unable to apply projectors. Are you sure ' 137 | 'that specified POSCAR and PROCAR file belong to ' 138 | 'the same crystal structure?') 139 | 140 | # We update total absolute weights to correspond to 141 | # unfolded phases (by multiplying them by the magnitude 142 | # ratio of unfolded and folded phases 143 | phase_ratio = np.abs(data[-1])/(np.abs(phases)+1e-4) 144 | 145 | for idim in xrange(data[-2].shape[3]): 146 | data[-2][:,:,:,idim,:] *= phase_ratio 147 | 148 | write_procar('{0}.irrep.{1}'.format(output, i), *data) 149 | 150 | 151 | if __name__ == '__main__': 152 | main() 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/unfolding/__pycache__/utils.cpython-34.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomkeus/vasp_unfold/d5ce631cc33e84803920c6191d5cf26c50eab58f/src/unfolding/__pycache__/utils.cpython-34.pyc -------------------------------------------------------------------------------- /src/unfolding/errors.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold 4 | # FILE: __errors__.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.4 8 | # DATE: December 12th 2017 9 | # 10 | #=========================================================== 11 | 12 | poscar_parse_error = """Unable to parse the input PROCAR file. Please check if the 13 | PROCAR file is properly formatted. 14 | 15 | PLEASE READ CAREFULLY 16 | 17 | Beware that output formatting in VASP is broken. Output field 18 | widths are not set to a large enough value in the source code. 19 | As a result, two common types of errors occur: 20 | 21 | 1. When a k-point has negative component, k-point output merges 22 | into a single string, for example 23 | 24 | k-point 75 : 0.25510204-0.74489796-0.00000000 weight = 0.00500000 25 | ^ ^ 26 | error error 27 | 28 | HOW TO FIX: 29 | 30 | Use sed to insert spaces between the components 31 | 32 | sed "s/\([0-9]\)\-\([0-9]\)/\\1 -\\2/g" PROCAR.broken > PROCAR.fixed 33 | 34 | 35 | 2. Orbital weight/phase precision requires field width larger than 36 | the field width allows. As a result, instead of orbital weight/ 37 | phase, asterisks are written. For example 38 | 39 | ion s py pz px dxy dyz dz2 dxz dx2 40 | 1 0.000 -0.292 0.000 -0.503 0.000 0.001 0.000 -0.001 0.000 41 | 1 0.000 -1.286 0.000 0.049 0.000 0.003 0.000 0.000 0.000 42 | 2 0.000 0.292 0.000 0.503 0.000 0.001 0.000 -0.001 0.000 43 | 2 0.000 1.286 0.000 -0.049 0.000 0.003 0.000 0.000 0.000 44 | 3 0.000 -0.003 0.000 0.006 0.000 0.002 0.000 -0.003 0.000 45 | 3 0.000 -0.015 0.000 -0.001 0.000 0.008 0.000 0.000 0.000 46 | 4 ***** 0.003 0.000 -0.006 0.000 0.002 0.000 -0.003 0.000 47 | 4 ***** 0.015 0.000 0.001 0.000 0.008 0.000 0.000 0.000 48 | ^ 49 | errors 50 | 51 | HOW TO FIX: 52 | 53 | Use sed to replace every asterisk with a zero 54 | 55 | sed "s/\*/0/g" PROCAR > PROCAR.edited 56 | 57 | 58 | So far no other sources of errors have been encountered. In order 59 | to solve them for good, either edit VASPs source code or bug VASPs 60 | developer until they fix it. 61 | """ 62 | -------------------------------------------------------------------------------- /src/unfolding/parse.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold 4 | # FILE: parse.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.4 8 | # DATE: December 12th 2017 9 | # 10 | #=========================================================== 11 | 12 | import numpy as np 13 | from utils import Getlines, post_error 14 | 15 | def parse_poscar(filename): 16 | '''Parses POSCAR file. Returns 3x3 float array 17 | containing unit cell vectors as rows, Nx3 18 | array containing fractional positions of atoms, 19 | N being number of atoms and a list of chemical 20 | symbols. 21 | ''' 22 | try: 23 | gl = Getlines(filename) 24 | except: 25 | post_error('Unable to open "{0}" for reading.'.format(filename)) 26 | 27 | # Skip the comment line 28 | gl.readline() 29 | 30 | # Read the scaling factor 31 | f = float(gl.readline()) 32 | 33 | # Read the unit cell vectors 34 | cell = np.zeros((3, 3), float) 35 | 36 | cell[0] = f*np.array(gl.readline().split(), float) 37 | cell[1] = f*np.array(gl.readline().split(), float) 38 | cell[2] = f*np.array(gl.readline().split(), float) 39 | 40 | # Read the chemical symbols 41 | syms = gl.readline().split() 42 | 43 | # Read the atom counts 44 | counts = np.array(gl.readline().split(), int) 45 | 46 | # Get the number of atoms 47 | natoms = np.sum(counts) 48 | 49 | # Rearrange into the long list of chemical symbols 50 | symbols = [] 51 | 52 | for s, c in zip(syms, counts): 53 | symbols += [s]*c 54 | 55 | # Cartesian or fractional coordinates? 56 | ctype = gl.readline()[0].lower() 57 | 58 | if ctype == 's': 59 | ctype = gl.readline()[0].lower() 60 | 61 | if ctype == 'c': 62 | mult = np.linalg.inv(cell) 63 | elif ctype == 'd': 64 | mult = np.eye(3) 65 | else: 66 | post_error('"{0}" is unknown POSCAR option'.format(ctype)) 67 | 68 | # Allocate storage for positions 69 | spos = np.zeros((len(symbols), 3)) 70 | 71 | # Read the positions 72 | for i in xrange(len(symbols)): 73 | spos[i] = np.array(gl.readline().split()[:3], float) 74 | 75 | # If necessary, this will convert from 76 | # Cartesian to fractional coordinates 77 | spos = np.dot(spos, mult) 78 | 79 | return cell, spos, symbols 80 | 81 | 82 | def parse_procar(filename, vasp_version): 83 | '''This function parses a PROCAR file. It returns a tuple 84 | consisting of following elements: 85 | 86 | orbitals - (norbs) string array of orbital labels (s, px, py etc...) 87 | kpoints - (npoints,3) float array of k-point coordinates 88 | kweights - (npoints) float array of k-point weights 89 | bands - (npoints,nbands,nspin) float array of band energies 90 | occupancies - (npoints,nbands,nspin) float array of band occupancies 91 | weights - (npoints,nions*norbs,nbands,ndim,nspin) float array 92 | of orbital weights 93 | phases - (npoints,nions*norbs,nbands,nspin) complex array of 94 | phases of orbital weights if LORBIT=12, otherwise None 95 | 96 | Where: 97 | 98 | norbs - number of orbitals (It can be 9 or 16 with f orbitals) 99 | npoints - number of k-points 100 | nbands - number of bands 101 | nspin - number of spins (1 for non spin-polarized, 2 otherwise) 102 | nions - number of atoms 103 | ndim - orbital weight dimensionality (1 for collinear, 4 otherwise) 104 | ''' 105 | 106 | try: 107 | gl = Getlines(filename) 108 | except: 109 | post_error('Unable to open "{0}" for reading.'.format(filename)) 110 | 111 | header_1 = gl.readline() 112 | header_2 = gl.readline().split() 113 | 114 | npoints = int(header_2[3]) 115 | nbands = int(header_2[7]) 116 | nions = int(header_2[-1]) 117 | 118 | # Remember the position in file 119 | start = gl.tell() 120 | 121 | # Skip two lines containing first k-point and band 122 | gl.readline() 123 | gl.readline() 124 | 125 | # Determine the number of orbitals 126 | orbitals = gl.readline().split()[1:-1] 127 | 128 | norbs = len(orbitals) 129 | 130 | # Allocate maximal storage. In case calculation was 131 | # non spin-polarized or collinear we can just trim 132 | # the excess components at the end 133 | kpoints = np.zeros((npoints, 3), float) 134 | kweights = np.zeros(npoints, float) 135 | bands = np.zeros((npoints, nbands, 2), float) 136 | occupancies = np.zeros((npoints, nbands, 2), float) 137 | weights = np.zeros((npoints, nions*norbs, nbands, 4, 2), float) 138 | 139 | dim = 0 140 | 141 | # Determine if the calculation was non-collinear 142 | # by counting how many lines in the first band 143 | # block begin with tot. That number will be equal 144 | # to the number of sub-blocks for orbital weights 145 | # (1 in case of collinear and 4 otherwise) 146 | while True: 147 | line = gl.readline() 148 | 149 | if line.startswith('tot'): 150 | dim += 1 151 | elif line.startswith('band'): 152 | break 153 | 154 | # This function will read block of absolute weights 155 | # for i-th k-point, j-th band and s-th spin component 156 | def get_absweights(i, j, s): 157 | # Skip line with orbital names 158 | gl.readline() 159 | 160 | for k in xrange(dim): 161 | # Fetch entire orbital weight block 162 | data = np.fromfile(gl, sep=" ", count=nions*(norbs+2)) 163 | 164 | # Cast it into tabular shape 165 | data = data.reshape((nions, norbs+2)) 166 | 167 | # Discard first and last columns and store weights 168 | weights[i,:,j,k,s] = data[:,1:-1].flatten() 169 | 170 | # Skip line with the totals 171 | gl.readline() 172 | 173 | # Check whether phase information is included 174 | if '+ phase' in header_1: 175 | # Allocate storage for phases 176 | phases = np.zeros((npoints, nions*norbs, nbands, 2), complex) 177 | 178 | if vasp_version < (5, 4, 4): 179 | # Declare nested function that handles 180 | # parsing of complex weights 181 | def get_weights(i, j, s): 182 | # Read abs values of weights 183 | get_absweights(i, j, s) 184 | 185 | # Skip line with orbital names 186 | gl.readline() 187 | 188 | # Fetch entire phase block 189 | data = np.fromfile(gl, sep=" ", count=2*nions*(norbs+1)) 190 | # Cast it into tabular shape 191 | data = data.reshape((2*nions, norbs+1)) 192 | 193 | # Discard first column and store real and imaginary 194 | # parts respectively 195 | phases[i,:,j,s] = data[::2,1:].flatten() 196 | phases[i,:,j,s] += 1j*data[1::2,1:].flatten() 197 | else: 198 | # Declare nested function that handles 199 | # parsing of complex weights 200 | def get_weights(i, j, s): 201 | # Read abs values of weights 202 | get_absweights(i, j, s) 203 | 204 | # Skip line with orbital names 205 | gl.readline() 206 | 207 | # Fetch entire phase block 208 | data = np.fromfile(gl, sep=" ", count=nions*(2*norbs+2)) 209 | # Cast it into tabular shape 210 | data = data.reshape((nions, 2*norbs+2)) 211 | 212 | # Discard first column and store real and imaginary 213 | # parts respectively 214 | phases[i,:,j,s] = data[:,1:-1:2].flatten() 215 | phases[i,:,j,s] += 1j*data[:,2:-1:2].flatten() 216 | 217 | # Skip line with charges 218 | gl.readline() 219 | else: 220 | # Phases are None in this case 221 | phases = None 222 | 223 | # In this case we just have absolutes of weights 224 | # No need for a new function 225 | get_weights = get_absweights 226 | 227 | # Go back to the beginning of the first k-point 228 | gl.seek(start) 229 | 230 | for i in xrange(npoints): 231 | # Parse k-point coordinates 232 | k_line = gl.readline().split() 233 | 234 | kpoints[i] = [float(k_line[c]) for c in [3, 4, 5]] 235 | kweights[i] = float(k_line[-1]) 236 | 237 | for j in xrange(nbands): 238 | # Parse band energy 239 | band_line = gl.readline().split() 240 | 241 | bands[i, j, 0] = float(band_line[4]) 242 | occupancies[i, j, 0] = float(band_line[-1]) 243 | 244 | # Parse orbital weights 245 | get_weights(i, j, 0) 246 | 247 | # Seek now for the second spin component 248 | res = gl.readline(False) 249 | 250 | # If there is no second spin component, finish by 251 | # returning just the first component 252 | if res is None: 253 | # Trim the second spin component 254 | bands = bands[:,:,:1] 255 | occupancies = occupancies[:,:,:1] 256 | weights = weights[:,:,:,:dim,:1] 257 | 258 | if phases is not None: 259 | phases = phases[:,:,:,:1] 260 | 261 | return [orbitals, kpoints, kweights, bands, occupancies, \ 262 | weights, phases] 263 | 264 | # Otherwise, read bands and weights for the second component 265 | for i in xrange(npoints): 266 | # Skip k-point coordinates 267 | gl.readline() 268 | 269 | for j in xrange(nbands): 270 | # Parse band energy 271 | band_line = gl.readline().split() 272 | 273 | bands[i, j, 1] = float(band_line[4]) 274 | occupancies[i, j, 1] = float(band_line[-1]) 275 | 276 | # Parse orbital weights 277 | get_weights(i, j, 1) 278 | 279 | return [orbitals, kpoints, kweights, bands, occupancies, \ 280 | weights[:,:,:,:dim,:], phases] 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/unfolding/unfolding.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold 4 | # FILE: unfolding.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.34 8 | # DATE: May 31st 2017 9 | # 10 | #=========================================================== 11 | 12 | import numpy as np 13 | from utils import post_error, frac_translation_order 14 | 15 | 16 | def build_operators(spos, trans, check_mapping=False, eps=1e-6): 17 | '''Given a list of fractional positions, and a list of 18 | fractional translations, produce a set of matrices, 19 | describing how fractional translations permute atomic 20 | positions within the unit cell. Two fractional positions 21 | si and sj are considered to be identical when |si-sj| 3: 57 | post_error('There can be at most three generators ' 58 | 'of fractional translations.') 59 | 60 | # Use folded generators as floats to perform linear independece tests 61 | tgensf = np.array(tgens, dtype=float)%1 62 | 63 | # Tolerance for linear independenec per dimension 64 | eps = 1e-2 65 | 66 | # Check if generators are linearly independent 67 | if len(tgens) == 2 and np.all(np.cross(tgensf[0], tgensf[1]) < eps**2): 68 | post_error('Generators are not linearly independant.') 69 | elif len(tgens) == 3 and np.linalg.det(np.array(tgensf)) < eps**3: 70 | post_error('Generators are not linearly independant.') 71 | 72 | # Expand the generator list to be a 3x3 matrix 73 | tgens = np.append(tgens, np.ones((3-len(tgens), 3)), axis=0) 74 | 75 | # Get the order of every generator 76 | order = np.array([frac_translation_order(g) for g in tgens], int) 77 | 78 | # Get the corresponding roots of unity which will be 79 | # used to construct irreps 80 | deltag = np.exp(-2*np.pi*1j/order) 81 | 82 | # Fold the translation vectors into the unit cell 83 | tgens = tgens % 1 84 | 85 | # Total number of translations 86 | ntrans = np.prod(order) 87 | 88 | # Storage for translations 89 | trans = np.zeros((ntrans, 3), float) 90 | 91 | # Calculate irreps of every generator 92 | irrepg = np.zeros((ntrans, 3), complex) 93 | 94 | # Loop over all possible products of powers of generators 95 | # and store the irreps 96 | for i in xrange(order[0]): 97 | for j in xrange(order[1]): 98 | for k in xrange(order[2]): 99 | ii = i*order[1]*order[2]+j*order[2]+k 100 | 101 | irrepg[ii] = deltag**[i, j, k] 102 | 103 | # Irrep table for all translations 104 | irreps = np.zeros((ntrans, ntrans), complex) 105 | 106 | # Loop over all posible products of powers of generators 107 | for i in xrange(order[0]): 108 | for j in xrange(order[1]): 109 | for k in xrange(order[2]): 110 | ti = i*order[1]*order[2]+j*order[2]+k 111 | 112 | # Get the translation vector 113 | trans[ti] = np.dot([i, j, k], tgens)%1 114 | 115 | # Get all irreps of that translations 116 | for l in xrange(ntrans): 117 | irreps[l, ti] = np.prod(irrepg[l]**[i, j, k]) 118 | 119 | return trans, irreps 120 | 121 | 122 | def build_projectors(irreps, ops, intdim=1): 123 | '''Builds irrep projection operators, given irreps 124 | and corresponding translation operator matrices. 125 | Intdim specifies how many orbitals per atomic site 126 | there are. 127 | ''' 128 | projs = np.zeros((len(irreps), ops.shape[1], ops.shape[2]), complex) 129 | 130 | # Loop over irreps 131 | for i, ii in enumerate(irreps): 132 | # Sum operators multiplied by their irrep 133 | for j, oj in enumerate(ops): 134 | projs[i] += irreps[i, j]*oj 135 | 136 | # Expand onto the orbital space and normalize 137 | return np.kron(projs, np.eye(intdim))/len(ops) 138 | -------------------------------------------------------------------------------- /src/unfolding/utils.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold 4 | # FILE: utils.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.4 8 | # DATE: December 12th 2017 9 | # 10 | #=========================================================== 11 | 12 | import numpy as np 13 | import argparse 14 | import sys 15 | import fractions 16 | import traceback 17 | 18 | 19 | def post_error(error_info, show_traceback=False): 20 | '''Write error message to sderr and exit program 21 | ''' 22 | message = '\nError: '+error_info+'\n\n' 23 | 24 | if show_traceback: 25 | # Show exception traceback 26 | tb_msg = traceback.format_exc() 27 | 28 | max_line_len = max(len(line) for line in tb_msg.split('\n')) 29 | 30 | message += 'Operation failed due to the following exception:\n' 31 | message += '='*max_line_len 32 | message += '\n' + traceback.format_exc() 33 | message += '='*max_line_len 34 | 35 | sys.stderr.write(message) 36 | 37 | exit() 38 | 39 | 40 | class Getlines(file): 41 | '''Small wrapper of the Python's built-in file 42 | class. It's purpose is to skip empty lines 43 | while reading the file''' 44 | 45 | def __init__(self, fname, comment=None): 46 | '''Constructor opens the file in the read mode''' 47 | super(Getlines, self).__init__(fname, 'r') 48 | self.comment = comment 49 | 50 | 51 | def __advance__(self, advance_func, eof_error): 52 | '''Advances along file line by line, skipping empty 53 | lines and stripping trailing and leading whitspaces 54 | and comments. advance_func specifies function which 55 | provides contents of next line. If eof_error is True 56 | error will be posted and execution terminated if end 57 | of file is reached. 58 | ''' 59 | line = '' 60 | 61 | while len(line) == 0: 62 | line = advance_func() 63 | 64 | if line == '': 65 | if eof_error is True: 66 | post_error('Reached end of: {0}.'.format(self.name)) 67 | else: 68 | return None 69 | if self.comment is not None: 70 | # Strip comments 71 | line = line[:line.find(self.comment)] 72 | 73 | # Strip spaces 74 | line = line.strip() 75 | 76 | return line 77 | 78 | 79 | def next(self): 80 | '''Overides file.next() so that empty lines are skipped 81 | and leading and trailing whitespaces and comments stripped. 82 | ''' 83 | return self.__advance__(super(Getlines, self).next, False) 84 | 85 | 86 | def readline(self, eof_error=True): 87 | '''Overides file.readline() so that empty lines are skipped 88 | and leading and trailing whitespaces and comments stripped. 89 | Optionally, if eof_error is True, error will be posted and 90 | execution terminated in case end of file is reached 91 | ''' 92 | return self.__advance__(super(Getlines, self).readline, eof_error) 93 | 94 | 95 | def translation(tstring): 96 | '''Parse string describing fractional translations. 97 | Valid form has comma separated components, where 98 | every component either 0 or 1/n, with n positive 99 | integer. During parsing 0 is replaced with 1/1 100 | and array of three fractions is returned. 101 | ''' 102 | try: 103 | return [fractions.Fraction(s) if s.strip() != "0" else 1 for s in tstring.split(',')] 104 | except: 105 | post_error('Unable to parse string: "{0}". Check help for valid ' 106 | 'translation generator specification'.format(tstring)) 107 | 108 | def version(vstring): 109 | '''Parse string containing version information. 110 | Valid form has dot separated digits. Returns tuple 111 | of integers which are to be lexicographically compared. 112 | ''' 113 | try: 114 | return tuple(int(d) for d in vstring.split('.')) 115 | except: 116 | post_error('Unable to parse string: "{0}". The valid version ' 117 | 'is composed of dot separated digists'.format(vstring)) 118 | 119 | def lcm(a, b): 120 | '''Return lowest common multiple.''' 121 | return a * b // fractions.gcd(a, b) 122 | 123 | 124 | def lcmm(*args): 125 | '''Return lcm of args.''' 126 | return reduce(lcm, args) 127 | 128 | 129 | def fraction_order(fract): 130 | '''Return the lowest integer multiple of a fraction 131 | that yields an integer. 132 | ''' 133 | if not isinstance(fract, fractions.Fraction): 134 | fract = fractions.Fraction(fract) 135 | 136 | return lcm(fract.numerator, fract.denominator) / fract.numerator 137 | 138 | 139 | def frac_translation_order(trans): 140 | '''Returns the lowest integer multiple of a fractional 141 | translation that yields translation with integer components. 142 | ''' 143 | order = [fraction_order(ti) for ti in trans] 144 | 145 | return lcmm(*order) 146 | -------------------------------------------------------------------------------- /src/unfolding/write.py: -------------------------------------------------------------------------------- 1 | #=========================================================== 2 | # 3 | # PROJECT: vasp_unfold 4 | # FILE: write.py 5 | # AUTHOR: Milan Tomic 6 | # EMAIL: tomic@th.physik.uni-frankfurt.de 7 | # VERSION: 1.32 8 | # DATE: Apr 25th 2017 9 | # 10 | #=========================================================== 11 | 12 | import numpy as np 13 | from utils import post_error 14 | 15 | 16 | def write_procar(fname, orbitals, kpoints, kweights, bands, 17 | occupations, weights, phases): 18 | '''Write PROCAR file based on supplied data 19 | ''' 20 | # Labels for orbitals 21 | orblabels = ['s', 'py', 'pz', 'px', 'dxy', 'dyz', 'dz2', 'dxz', 'dx2', 22 | 'f-3', 'f-2', 'f-1', 'f0', 'f1', 'f2', 'f3'] 23 | 24 | norb = len(orbitals) 25 | npoints = len(kpoints) 26 | nbands = bands.shape[1] 27 | nions = weights.shape[1]/norb 28 | nspin = weights.shape[-1] 29 | ndim = weights.shape[-2] 30 | 31 | try: 32 | out = open(fname, 'w') 33 | except: 34 | post_error('Unable to open "{0}" for writing'.format(fname)) 35 | 36 | # Write the first line of the PROCAR file 37 | if phases is not None: 38 | out.write('PROCAR lm decomposed + phase\n') 39 | else: 40 | out.write('PROCAR lm decomposed\n') 41 | 42 | # Format out the column title line for orbital weights 43 | orb_ttl_1 = 'ion ' 44 | orb_ttl_2 = 'ion ' 45 | 46 | for i in xrange(norb): 47 | orb_ttl_1 += '{0: >6} '.format(orblabels[i]) 48 | orb_ttl_2 += '{0: >6} '.format(orblabels[i]) 49 | 50 | orb_ttl_1 += '{0: >6}\n'.format('tot') 51 | orb_ttl_2 += '\n' 52 | 53 | for s in xrange(nspin): 54 | # Write the second line containing the sizes 55 | out.write('# of k-points: {0} # of bands: {1}' 56 | ' # of ions: {2}\n\n'.format(npoints, nbands, nions)) 57 | 58 | for i, k in enumerate(kpoints): 59 | # Write the k-point info 60 | out.write(' k-point {0: >4} : '.format(i+1)) 61 | out.write('{0:.8f} {1:.8f} {2:.8f} '.format(*k)) 62 | out.write('weight = {0:.8f}\n\n'.format(kweights[i])) 63 | 64 | for j, b in enumerate(bands[i,:,s]): 65 | # Write the band info 66 | out.write('band {0: >4} # '.format(j+1)) 67 | out.write('energy {0: >13.8f} # '.format(b)) 68 | out.write('occ. {0: >11.8f}\n\n'.format(occupations[i, j, s])) 69 | 70 | # Write absolute weight blocks. In case of 71 | # non-collinear calculation, there is four 72 | # such blocks 73 | for d in xrange(ndim): 74 | if d == 0: 75 | # Write names of orbitals (s, px, py etc...) 76 | out.write(orb_ttl_1) 77 | 78 | # Allocate storage for accumulation of orbital totals 79 | tot_orb = np.zeros(norb, float) 80 | 81 | # Loop over individual atoms 82 | for k in xrange(nions): 83 | # Write atom's index 84 | out.write('{0: >3} '.format(k+1)) 85 | 86 | # Extract corresponding weights 87 | w = weights[i, k*norb:(k+1)*norb, j,d,s] 88 | 89 | # Add to totals 90 | tot_orb += w 91 | 92 | # Write out row of weights 93 | for l in xrange(norb): 94 | out.write('{0: >6.3f} '.format(w[l])) 95 | 96 | # Write atom total 97 | out.write('{0: >6.3f}\n'.format(np.sum(w))) 98 | 99 | # We will now write line with orbital totals 100 | out.write('tot ') 101 | 102 | for l in xrange(norb): 103 | out.write('{0: >6.3f} '.format(tot_orb[l])) 104 | 105 | # Finally, atom+orbital total 106 | out.write('{0: >6.3f}\n'.format(np.sum(tot_orb))) 107 | 108 | # If we have phases we write them now 109 | if phases is not None: 110 | # Write again names of orbitals 111 | out.write(orb_ttl_2) 112 | 113 | # Loop over atoms 114 | for k in xrange(nions): 115 | # Write atom index 116 | out.write('{0: >3} '.format(k+1)) 117 | 118 | # Extract corresponding phase 119 | phs = phases[i, k*norb:(k+1)*norb, j, s] 120 | 121 | # Write real part 122 | for l in xrange(norb): 123 | out.write('{0: >6.3f} '.format(phs[l].real)) 124 | 125 | # Write atom index 126 | out.write('\n{0: >3} '.format(k+1)) 127 | 128 | # Write imaginary part 129 | for l in xrange(norb): 130 | out.write('{0: >6.3f} '.format(phs[l].imag)) 131 | 132 | out.write('\n') 133 | 134 | out.write('\n') 135 | 136 | out.write('\n') 137 | 138 | out.close() 139 | 140 | --------------------------------------------------------------------------------