From 994525bb5a2323ebc57c4c975db3178145327f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20T=2E=20Jochym?= Date: Tue, 31 Dec 2013 11:31:47 -0600 Subject: [PATCH] Initial population. Packege setup. Nothing working yet. --- qeutil/__init__.py | 37 + qeutil/analyzers.py | 115 +++ qeutil/quantumespresso.io.py | 1427 ++++++++++++++++++++++++++++++++++ qeutil/quantumespresso.py | 1246 +++++++++++++++++++++++++++++ qeutil/readers.py | 472 +++++++++++ qeutil/writers.py | 124 +++ setup.cfg | 3 + setup.py | 16 + 8 files changed, 3440 insertions(+) create mode 100644 qeutil/__init__.py create mode 100644 qeutil/analyzers.py create mode 100644 qeutil/quantumespresso.io.py create mode 100644 qeutil/quantumespresso.py create mode 100644 qeutil/readers.py create mode 100644 qeutil/writers.py create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/qeutil/__init__.py b/qeutil/__init__.py new file mode 100644 index 0000000..435fb99 --- /dev/null +++ b/qeutil/__init__.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +"""This module defines an interface to Quantum-Espresso. +http://www.quantum-espresso.org/ +""" + + + +from ase import Atom, Atoms +import os +from glob import glob +from os.path import join, isfile, islink + +import numpy as np + +from ase.data import atomic_numbers +from ase.units import Bohr, Hartree, Rydberg, fs +from ase.data import chemical_symbols +from ase.calculators.calculator import FileIOCalculator, Parameters, kpts2mp, ReadError + +from quantumespresso.io import * + + +class QuantumEspresso(FileIOCalculator): + """Class for doing Quantum Espresso calculations. + + The default parameters are very close to those that + the QE Fortran code would use. These are the exceptions:: + + """ + + implemented_properties = ['energy', 'forces', 'stress'] + + pass + + diff --git a/qeutil/analyzers.py b/qeutil/analyzers.py new file mode 100644 index 0000000..dac1a27 --- /dev/null +++ b/qeutil/analyzers.py @@ -0,0 +1,115 @@ +def get_EOS(d, comment=""): + # Fitting functions + def BMEOS(v,v0,b0,b0p): + return (b0/b0p)*(pow(v0/v,b0p) - 1) + fitfunc = lambda p, x: [BMEOS(xv,p[0],p[1],p[2]) for xv in x] + errfunc = lambda p, x, y: fitfunc(p, x) - y + + pv=array([d[:,0]**3,d[:,1]]).T + + # Estimate the initial guess assuming b0p=1 + # Limiting volumes + v1=min(pv[:,0]) + v2=max(pv[:,0]) + # The pressure is falling with the growing volume + p2=min(pv[:,1]) + p1=max(pv[:,1]) + b0=(p1*v1-p2*v2)/(v2-v1) + v0=v1*(p1+b0)/b0 + # Initial guess + p0=[v0,b0,1] + #Fitting + #print p0 + fit, succ = optimize.leastsq(errfunc, p0[:], args=(pv[:,0],pv[:,1])) + + # Ranges - the ordering in pv is not guarateed at all! + # In fact it may be purely random. + x=numpy.array([min(pv[:,0]),max(pv[:,0])]) + y=numpy.array([min(pv[:,1]),max(pv[:,1])]) + + # Plot the P(V) curves and points for the crystal + # Plot the points + plot(pv[:,0]**1/3,pv[:,1],'.',label='Calc. '+comment) + + # Mark the center P=0 V=V0 + axvline(fit[0]**1/3,ls='--') + axhline(0,ls='--') + + # Plot the fitted B-M EOS through the points + xa=numpy.linspace(x[0],x[-1],20) + plot(xa**1/3,fitfunc(fit,xa),'-', label="B-M fit:\n$V_0$=%f ($A_0$=%f),\n$B_0$=%f kBar,\n$B'_0$=%f " + % (fit[0], fit[0]**(1.0/3), fit[1], fit[2]) ) + legend() + ylabel('Pressure (kBar)') + xlabel('Lattice constant ($\mathrm{\AA}$)') + draw(); + return fit, pv + + +def analize_dir(idx, ex=True, bdir='/home/jochym/Desktop/Fizyka/', + qp=array([G1,X,G2,L]), lbl=None, ax=None, castep=False): + s=0 + t=[] + for x in [0]+map(norm,qp[1:]-qp[:-1]): + s+=x + t.append(s) + + par=calcData[idx] + + try: + frq=loadtxt(bdir + idx +'/'+par['prefix']+'.freq.gp') + except IOError : + print "Cannot open frequency file in", wd + return + if ax : + ax1=ax[0] + else : + ax1=subplot2grid([1,4],[0,3],colspan=1) + + phdos=loadtxt(bdir+idx+'/'+params['prefix']+'.dos') + clr=ax1.plot(phdos[:,1],cminv2meV*phdos[:,0])[0].get_color() + ax1.yaxis.set_label_position("right") + ax1.yaxis.tick_right() + ax1.xaxis.tick_top() + + if ax : + ax2=ax[1] + else : + ax2=subplot2grid([1,4],[0,0],colspan=3,sharey=ax1) + + + clr=None + + if lbl : + lbltxt=", ".join(["%s" % (l,) for l in lbl if not (l in par.keys())]) + lbltxt+=", ".join(["%s" % (par[l],) for l in lbl if l in par.keys()]) + else : + lbltxt=idx + + for i in range(frq.shape[1]-1): + if clr : + ax2.plot(frq[1:,0], cminv2meV*frq[1:,i+1],'.',color=clr) + else : + clr=ax2.plot(frq[1:,0], cminv2meV*frq[1:,i+1],'.',label=lbltxt)[0].get_color() + + + if ex : + frqexp1=loadtxt('thO2-phon.png.dat') + clr=ax2.plot(frqexp1[:,0],frqexp1[:,1],'o')[0].get_color(); + frqexp2=loadtxt('thO2-phon-2.dat') + ax2.plot(t[2]-frqexp2[:,0],frqexp2[:,1],'o',color=clr)[0]; + frqexp3=loadtxt('thO2-phon-3.dat') + ax2.plot(t[2]+frqexp3[:,0],frqexp3[:,1],'o',color=clr,label='Experiment')[0]; + if castep : + frqexp4=loadtxt('castep-take2.csv') + ax2.plot((t[3]-t[0])*frqexp4[:,0], frqexp4[:,1],'o', label='CASTEP') + + + title("M-P grid (e.s.): %(kx)dx%(ky)dx%(kz)d (phon): %(nq)d^3" % par) + xlim(0,max(frq[1:,0])) + ylim(0,ylim()[1]) + #xticks(t,[u'Γ','X',u'Γ','L']) + vlines(xticks()[0][1:-1],ylim()[0],ylim()[1],linestyles='--') + ylabel('Energy (meV)') + subplots_adjust(wspace=0) + return [ax1,ax2] diff --git a/qeutil/quantumespresso.io.py b/qeutil/quantumespresso.io.py new file mode 100644 index 0000000..24205c5 --- /dev/null +++ b/qeutil/quantumespresso.io.py @@ -0,0 +1,1427 @@ +""" +This module contains functionality for the QUANTUM ESPRESSO calculator +in order to: + + - read the results in text and xml format + - read a .in file and return all the data + - build an Atoms objects starting from an .in file + + +routines: + + +def read_quantumespresso_textoutput(filename): +def read_quantumespresso_xmloutput(filename): + +def write_atoms(infilename, myprefix="dry_pwscf", verbose = False): +def read_in_file(infilename): + + +""" + +import os +import os.path +import string + +import sys +try: + import xml.etree.cElementTree as ET +except ImportError: + try: + import xml.etree.ElementTree as ET + except ImportError: + print("unable to import ElementTree") + + +# ------------------------------------------------------------------------------------------------------------------------------- +# +# read text output +# +# ------------------------------------------------------------------------------------------------------------------------------- + +def read_quantumespresso_textoutput(filename, verbose=False): + """ + read the output text file from a QE run and extracts physical quantities + returns a dictionary with the quantities + """ + + alltext = open(filename).read().lower() # reads all the lines in the text file and converts them in + # lowercase + + ## [RFC] some 'assert' statements about NOT having some error string in the text + + alllines = alltext.split('\n') # creates a list of lines from the text stream + + #kpts_num = None + #nbands = None + #nelect = None + _no_spin_orbit = None + Results = {} + + + Results['error'] = False + Results['error_message'] = [] + Results['ExchangeCorrelation'] = None + + Results['error_found'] = False + + matches=[0] # the list will store the line numbers at which each iteration ends + # in that way, in case of multiple iterations, we will be allowed to + # excerpt the last one + for n, l in enumerate(alllines): # [RFC] are there other useful infos in the preambole? + + if l.rfind("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") > -1: + if Results['error'] : + Results['error_message'].append(l) + break; + Results['error'] = True + + if Results['error']: + Results['error_message'].append(l) + + if (l.strip()=='writing output data file pwscf.save'): + matches.append(n) + + if l.rfind('number of atoms/cell') > -1: + t = l.split() + pos = t.index('=') + Results['nat'] = int(t[pos+1]) + + if l.rfind('number of electrons') > -1: + t = l.split() + pos = t.index('=') + Results['nelec'] = float(t[pos+1]) + + if l.rfind('kohn-sham states') > -1: # the number of bands + t = l.split() + pos = t.index('states=') + Results['nbands'] = int(t[pos+1]) + + if l.rfind('number of k points=') > -1: # the number of kpoints + t = l.split() + pos = t.index('points=') + Results['kpts_num'] = int(t[pos+1]) + + if l.rfind('exchange-correlation') > -1: + t = l.split() + pos = t.index('=') + Results['exchange_correlation'] = t[pos+1:len(t)-1] + + if l.rfind('without spin-orbit') > -1: + _no_spin_orbit = True + + if(len(matches)>2): + # The last iteration has no data on k-points, cell, positions, etc. + # We need to grab it from the one before last + lines = alllines[matches[-3]:matches[-1]] + else: + lines = alllines + + + alltext = []; # some memory garbage collection would be good here + alllines = []; + + Results['EnergyFound'] = False + CAUGHT = -1 + + if (Results['error']): + return False + + +# self.etotal = None; +# self.etotal_accuracy = None; + Results['niter'] = None + Results['pressure'] = None + Results['stress'] = None + Results['total_magnetization'] = None + Results['absolute_magnetization'] = None + Results['fermi_energy'] = None + Results['kpts'] = None + Results['atoms_forces'] = None +# _old_cell = self.cell.copy() + Results['cell'] = None + Results['atomic_positions'] = None + + + for n, l in enumerate(lines): # loop on the lines from output + + # some quantities, like the stress tensor or the kpoints + # span several lines. we use the trick of tagging the + # begin of those lines by setting the variable "CAUGHT" + # A non-negative value means that we are still inside + # such a sub-block. The number of lines still left in the + # sub-block is given by the value of CAUGHT, which is + # decreased by one at each line. + + #print '>>>', l + + if CAUGHT < 0: + # we are not in a sub-block + # find Total Energy and accuracy + if len(l)>0 and (l[0] == '!'): # and l[1] == 'total' and l[2] == 'energy'): + t = l.split(); + if(t[1] == 'total' and t[2] == 'energy'): + #print "ENERGY FOUND" + pos = t.index('ry') + Results['etotal'] = float(t[pos-1]) # total energy in Rydberg + Results['EnergyFound'] = True + + elif Results['EnergyFound'] and l.rfind('scf accuracy') > -1: + #print "ACCURACY" + t = l.split() + pos = t.index('ry') + Results['etotal_accuracy'] = float(t[pos-1]) + + if l.rfind('fermi energy') > -1: + t = l.split() + pos = t.index('is') + Results['fermi_energy'] = float(t[pos+1]) + + if l.rfind('magnetization') > -1: + t = l.split() + pos = t.index('magnetization') + if(t[pos-1] == 'total'): + Results['total_magnetization'] = float(t[pos+2]) + elif(t[pos-1] == 'absolute'): + Results['absolute_magnetization'] = float(t[pos+2]) + + if l.rfind('convergence has been achieved in') > -1: + t = l.split() + pos = t.index('iterations') + Results['niter'] = int(t[pos-1]) + + if l.rfind('lattice parameter (alat)') > -1: + t = l.split() + pos = t.index('=') + Results['alat'] = float(t[pos+1]) + + if l.rfind('stress') > -1: # find the stress tensor + t = l.split() + pos = t.index('stress') + if (pos > 0 and t[pos-1]=='total'): + pos = l.rfind('p=') + if pos > -1: # find the pressure + Results['pressure'] = float(l[pos+2:]) + # set-up to manage the sub-block + CAUGHT = 3 + CAUGHT_DISCARD=0 + CAUGHT_WHAT = 'stress' + CAUGHT_LINES = [] + + if l.rfind('number of k points=') > -1: # find the kpoints + #print "K-POINTS" + CAUGHT = Results['kpts_num'] + CAUGHT_DISCARD = 1 # discard 1 line at the beginning + CAUGHT_WHAT = 'kpts' + CAUGHT_LINES = [] + + + if l.rfind('new k-points:') > -1: # find the kpoints + #print "new K-POINTS" + CAUGHT = Results['kpts_num'] + CAUGHT_DISCARD = 0 # discard 0 line at the beginning + CAUGHT_WHAT = 'kpts' + CAUGHT_LINES = [] + + if l.rfind('forces acting on atoms') > -1: # find forces acting on atoms + CAUGHT = Results['nat'] + CAUGHT_DISCARD = 1 + CAUGHT_WHAT = 'forces' + CAUGHT_LINES = [] + + if l.rfind('crystal axes:') > -1: + CAUGHT = 3 + CAUGHT_DISCARD = 0 + CAUGHT_WHAT = 'old cell' + CAUGHT_LINES = [] + + if l.rfind('final estimate of lattice vectors') > -1: + CAUGHT = 3 + CAUGHT_DISCARD = 0 + CAUGHT_WHAT = 'new cell' + CAUGHT_LINES = [] + + if l.rfind('begin final coordinates') > -1: + CAUGHT = 3 + CAUGHT_DISCARD = 3 + CAUGHT_WHAT = 'new cell' + CAUGHT_LINES = [] + + if l.rfind('atomic_positions') > -1: + CAUGHT= Results['nat'] + CAUGHT_DISCARD = 0 + CAUGHT_WHAT = 'positions' + CAUGHT_LINES = [] + + elif CAUGHT > 0: # We are inside a sub-blok + #print CAUGHT_WHAT, CAUGHT + if(CAUGHT_DISCARD == 0): # gets the line but if this line should be + CAUGHT_LINES.append(l) # discarded instead + CAUGHT -= 1 + else: + CAUGHT_DISCARD -= 1 + + else: # process the caught lines + + if(CAUGHT_WHAT == 'stress'): + # get the stress tensor in kbar + Results['stress']=[] + number = len(CAUGHT_LINES) # i.e. 3, in this case + for n in range(number): + Results['stress'].append([float(el) for el in CAUGHT_LINES[n].split()[3:6]]) #NOTE it is in kbar + + elif (CAUGHT_WHAT == 'kpts'): # get the kpoints + Results['kpts'] = [] + Results['kpts_wk'] = [] + number = len(CAUGHT_LINES) + for n in range(number): + Results['kpts'].append([float(get_number(el)) for el in CAUGHT_LINES[n].split()[4:7]]) + Results['kpts_wk'].append([float(CAUGHT_LINES[n].split()[9])]) + + elif (CAUGHT_WHAT == 'forces'): + Results['atoms_forces'] = [] + number = len(CAUGHT_LINES) + for n in range(number): + Results['atoms_forces'].append([float(get_number(el)) for el in CAUGHT_LINES[n].split()[6:9]]) + + # elif (CAUGHT_WHAT == 'old cell'): + # old_cell = [] + # number = len(CAUGHT_LINES) + # for n in range(number): + # old_cell.append([float(el) for el in CAUGHT_LINES[n].split()[3:5]]) + + elif (CAUGHT_WHAT == 'new cell'): + Results['cell'] = [] + number = len(CAUGHT_LINES) + for n in range(number): + Results['cell'].append([float(el) for el in CAUGHT_LINES[n].split()]) + + elif (CAUGHT_WHAT == 'positions'): + Results['atomic_positions'] = [] + number = len(CAUGHT_LINES) + for n in range(number): + Results['atomic_positions'].append([float(el) for el in CAUGHT_LINES[n].split()[1:4]]) + + CAUGHT_LINES = None + CAUGHT_WHAT = None + CAUGHT = -1 + + + + if verbose: + print "etotal is : %s " % str(Results['etotal']) + print "etotal accuracy is : %s " % str(Results['etotal_accuracy']) + print "niter is : %s " % str(Results['niter']) + # print "nelect is : %s " % str(Results['nelect']) + print "Results nbands is : %s " % str(Results['nbands']) + print "Fermi energy is : %s " % str(Results['fermi_energy']) + print "tot magnetizations : %s " % str(Results['total_magnetization']) + print "abs magnetizations : %s " % str(Results['absolute_magnetization']) + print "ExchangeCorrelation : " + if (not Results['ExchangeCorrelation'] is None): + print "%s " % [str(el) for el in Results['ExchangeCorrelation']] + else: + print "None" + if(not Results['pressure'] is None): + print "Pressure is : %s " % str(Results['pressure']) + if(not Results['stress'] is None): + print "Stress is : %s " % str(Results['stress']) + print "kpoints found : %s " % str(Results['kpts_num']) + print "kpoints are : " + if(not Results['kpts'] is None): + for n in range(len(Results['kpts'])): + print "\t %s wk: %s" % (str(Results['kpts'][n]), str(Results['kpts_wk'][n])) + if(not Results['atoms_forces'] is None): + print "forces are : " + print Results['atoms_forces'] + print "alat is : %s" % str(Results['alat']) + if (not Results['cell'] is None): + print "cell is : " + print Results['new_cell'] + if( not Results['atomic_positions'] is None): + print "atomic positions : " + print Results['new_atomic_positions'] + + return Results + + +def get_bands_for_kpoints(mylines, nk, nb): + energy_array=[] + for n, l in enumerate(mylines): + + if l.expandtabs(1).isspace(): + pass + t = l.split() + if(t[0] == 'k'): + pass + energy_array.append([float(el) for el in t]) + + + +def get_number(string): + for t in string.split(): + array = [] + for c in t: + if (c.isdigit() or (c in '.e+-')): + array.append(str(c)) + return ''.join([c for c in array]) + + + + + + +# ------------------------------------------------------------------------------------------------------------------------------- +# +# read xml output +# +# ------------------------------------------------------------------------------------------------------------------------------- + +bravais_lattice_to_ibrav = {"free":0, + "cubic p (sc)": 1, + "cubic f (fcc)": 2, + "cubic i (bcc)": 3, + "hexagonal and trigonal p": 4, + "trigonal r": 5, + "tetragonal p (st)": 6, + "tetragonal i (bct)": 7, + "orthorhombic p": 8, + "orthorhombic base-centered(bco)": 9, + "orthorhombic face-centered": 10, + "orthorhombic body-centered": 11, + "monoclinic p": 12, + "monoclinic base-centered": 13, + "triclinic p": 14} + + +def str2bool(v): + """ + convert a string that has logical meaning into a boolean + """ + return v.lower() in ("true", "t", "yes", "1", "ok") + +def xml_openroot(xmlFile): + """ + open the xml file and return the root node + """ + root = ET.parse(xmlFile).getroot() + return root + + +def xml_index(element): + """ + print all the child found in element with their attributes (not recursively) + """ + for child in element: + print child.tag, child.attrib + + +def get_qexml_version(tree, mydict): + """ + get the xml format + """ + element = tree.find("HEADER") + mydict['qexml_version'] = element.find("FORMAT").attrib["VERSION"].strip() + mydict['qexml_after_1.4.0'] = mydict['qexml_version'] >= "1.4.0" + + +def get_eigenvalues_for_kpoint_xml(xmlfile): + """ + return the eigenvalues associated with kpoints + """ + kproot = ET.parse(xmlfile).getroot() + nbnd = int(kproot.find('INFO').attrib['nbnd']) + + eigenvalues = [float(a) for a in kproot.find('EIGENVALUES').text.split('\n') if not (a.isspace() or not a)] + occupations =[float(a) for a in kproot.find('OCCUPATIONS').text.split('\n') if not (a.isspace() or not a)] + + return eigenvalues, occupations + + +def get_k_points_xml(tree, mydict): + """ + return the kpoints information, eigenvalues and weights + """ + + if mydict.has_key('got_kpoints'): + if mydict['got_kpoints']: + return + + + mydict['nkstot'] = int(tree.find("BRILLOUIN_ZONE").find("NUMBER_OF_K-POINTS").text.strip()) + nkstot = mydict['nkstot'] + if mydict['lsda']: + mydict['nkpoints'] = mydict['nkstot'] / 2 + else: + mydict['nkpoints'] = mydict['nkstot'] + + element = tree.find("EIGENVALUES") + + mydict['kpoints_coordinates'] = [] + mydict['kpoints_weights'] = [] + mydict['kpoints_eigenvalues'] = [] + mydict['kpoints_occupations'] = [] + + for kp in range(0, nkstot): + tag = "./K-POINT."+str(kp+1) + kpoint = element.find(tag) + + mydict['kpoints_coordinates'].append([float(a) for a in kpoint.find('K-POINT_COORDS').text.split()]) + mydict['kpoints_weights'].append(float(kpoint.find('WEIGHT').text.strip())) + kpoints_datafile = kpoint.find('DATAFILE').attrib['iotk_link'] + eigenv, occup = get_eigenvalues_for_kpoint_xml(os.path.dirname(mydict['filename'])+'/'+kpoints_datafile) + mydict['kpoints_eigenvalues'].append(eigenv) + mydict['kpoints_occupations'].append(occup) + + mydict['got_kpoints'] = True + + +def get_cell_xml(tree, mydict): + """ + read in cell information: lattice vectos, cell factor.. + it adds the following keys to the dictionary mydict: + + NP_cell_correction, bravais_lattice, bravais_index, alat, + lattive_vector_units, lattice_vector, lattice_rvector, + moving_cell, cell_factor + """ + if mydict.has_key('got_cell'): + if mydict['got_cell']: + return + + element = tree.find("CELL") + mydict['NP_cell_correction'] = element.find("NON-PERIODIC_CELL_CORRECTION").text.strip() + + mydict['bravais_lattice'] = element.find("BRAVAIS_LATTICE").text.strip().lower() + mydict['bravais_index'] = bravais_lattice_to_ibrav[mydict['bravais_lattice']] + + mydict['alat'] = float(element.find("LATTICE_PARAMETER").text.strip()) + + sub_el = element.find("DIRECT_LATTICE_VECTORS") + mydict['lattice_vector_units'] = sub_el.find("UNITS_FOR_DIRECT_LATTICE_VECTORS").attrib['UNITS'].lower().strip() + v1 = [float(a) for a in sub_el.find("a1").text.strip().split()] + v2 = [float(a) for a in sub_el.find("a2").text.strip().split()] + v3 = [float(a) for a in sub_el.find("a3").text.strip().split()] + mydict['lattice_vector'] = [v1, v2, v3] + + sub_el = element.find("RECIPROCAL_LATTICE_VECTORS") + lattice_vector_units = sub_el.find("UNITS_FOR_RECIPROCAL_LATTICE_VECTORS").attrib['UNITS'].lower().strip() + v1 = [float(a) for a in sub_el.find("b1").text.strip().split()] + v2 = [float(a) for a in sub_el.find("b2").text.strip().split()] + v3 = [float(a) for a in sub_el.find("b3").text.strip().split()] + mydict['lattice_rvector'] = [v1, v2, v3] + + + subel = element.find("MOVING_CELL") + if not subel is None: + mydict['moving_cell'] = subel.text + mydict['cell_factor'] = float(moving_cell.strip()) + else: + mydict['moving_cell'] = None + mydict['cell_factor'] = None + + + mydict['got_cell'] = True + + +def get_planewaves_xml(tree, mydict): + """ + read in plain waves associated quantities + adds the following keys to the dictionary mydict: + + cutoff_units, cutpff_wfc, cutoff_rho, npwx (=max_number_og_gk_vectors), + gamma_only, dfftp, ngm_g, dffts, ngms_g, got_pwaves + + """ + if mydict.has_key('got_pwaves'): + if mydict['got_pwaves']: + return + + element = tree.find("PLANE_WAVES") + mydict['cutoff_units'] = element.find("UNITS_FOR_CUTOFF").attrib['UNITS'].lower().strip() + + mydict['cutoff_wfc'] = float(element.find("WFC_CUTOFF").text.strip()) + mydict['cutoff_rho'] = float(element.find("RHO_CUTOFF").text.strip()) + + mydict['npwx'] = int(element.find("MAX_NUMBER_OF_GK-VECTORS").text.strip()) + + mydict['gamma_only'] = str2bool(element.find("GAMMA_ONLY").text.strip()) + + attrib = element.find("FFT_GRID").attrib + mydict['dfftp'] = [float(a) for a in attrib.values()] + + mydict['ngm_g'] = int(element.find("GVECT_NUMBER").text.strip()) + + attrib = element.find("SMOOTH_FFT_GRID").attrib + mydict['dffts'] = [float(a) for a in attrib.values()] + + mydict['ngms_g'] = int(element.find("SMOOTH_GVECT_NUMBER").text.strip()) + + mydict['got_pwaves'] = True + + +def get_dimensions_xml(tree, mydict): + """ + read in "dimensions", that is variables that defines the arrays dimension + add the following keys to the dictionary + + nat (int): num. of atoms + nspecies (int): num. of species + nsym (int): num. of symmetries + nsym_brvais (int) + lsda (bool) + noncolin (bool) + smearing (string) + ntetra (int) : number of tetrahedra + nkpstot Int) : number of kpoints (total: if lsda is true it is multiplied by 2) + nelec (int) : num. of electrons + nbnd (nt) : number of bands + + """ + if mydict.has_key('got_dimensions'): + if mydict['got_dimensions']: + return + + + # IONS + element = tree.find("IONS") + mydict['nat'] = int(element.find("NUMBER_OF_ATOMS").text.strip()) + mydict['nspecies'] = int(element.find("NUMBER_OF_SPECIES").text.strip()) + + # SYMMETRIES + element = tree.find("SYMMETRIES") + + mydict['nsym'] = int(element.find("NUMBER_OF_SYMMETRIES").text.strip()) + mydict['nsym_bravais'] = int(element.find("NUMBER_OF_BRAVAIS_SYMMETRIES").text.strip()) + + # PLANE WAVES + + # SPIN + element = tree.find("SPIN") + mydict['lsda'] = str2bool(element.find("LSDA").text.strip()) + mydict['noncolin'] = str2bool(element.find("NON-COLINEAR_CALCULATION").text.strip()) + + # OCCUPATIONS + element = tree.find("OCCUPATIONS") + mydict['smearing'] = str2bool(element.find("SMEARING_METHOD").text.strip()) + el = element.find("NUMBER_OF_TETRAHEDRA") + if el is None: + mydict['ntetra'] = 0 + else: + mydict['ntetra'] = int(el.text.strip()) + + # BRILLOUIN ZONE + element = tree.find("BRILLOUIN_ZONE") + mydict['nkpstot'] = int(element.find("NUMBER_OF_K-POINTS").text.strip()) + if mydict['lsda'] is True: + mydict['nkpstot'] *= 2 + + # BAND STRUCTURE + element = tree.find("BAND_STRUCTURE_INFO") + mydict['nelec'] = float(element.find("NUMBER_OF_ELECTRONS").text.strip()) + mydict['nbnd'] = int(element.find("NUMBER_OF_BANDS").text.strip()) + + mydict['got_dimensions'] = True + + +def get_ions_xml(tree, mydict): + """ + read in quantitiesd related to the atoms + adds the following keys to the dictionary: + + nat (int) : num of atoms (also added by get_dimensions_xml + nsp (int) : num opf species (the same than nspecies) + + atoms, atoms_index, atoms_tau, + atoms_if_pos : these are lists + 'atoms' entries are dictionaries with keys: + {`specie','mass', 'PPfile'} + 'atoms_tau' and atoms_if_pos entries are lists + with 3 elements + + """ + + if mydict.has_key('got_ions'): + if mydict['got_ions']: + return + + if not mydict['got_cell']: + get_cell_xml(tree, mydict) + + element = tree.find("IONS") + + mydict['nat'] = int(element.find("NUMBER_OF_ATOMS").text.strip()) + mydict['nsp'] = int(element.find("NUMBER_OF_SPECIES").text.strip()) + + mydict['atoms'] = [] + mydict['atoms_index'] = [] + mydict['atoms_tau'] = [] + mydict['atoms_if_pos'] = [] + + + if mydict['qexml_after_1.4.0']: + for i in range(mydict['nsp']): + this_atom = {} + subel = element.find("SPECIE."+str(i+1)) + + this_atom['specie'] = subel.find("ATOM_TYPE").text.strip() + this_atom['mass'] = float(subel.find("MASS").text.strip()) + this_atom['PPfile'] = subel.find("PSEUDO").text.strip() + mydict['atoms'].append(this_atom) + else: + ValueError, "qexml version is before 1.4.0. This has not been implemented" + + mydict['pseudo_dir'] = element.find("PSEUDO_DIR").text.strip() + + alat = mydict['alat'] + for i in range(mydict['nat']): + subel = element.find("ATOM."+str(i+1)) + + mydict['atoms_index'].append(int(subel.attrib["INDEX"])) + mydict['atoms_tau'].append([float(s)/alat for s in subel.attrib["tau"].split()]) + mydict['atoms_if_pos'].append([int(s) for s in subel.attrib["if_pos"].split()]) + + mydict['got_ions'] = True + +def get_efield_xml(tree, mydict): + """ + read electric field data + """ + + if mydict.has_key('got_efield'): + if mydict['got_efield']: + return + + element = tree.find("ELECTRIC_FIELD") + if not element is None: + + mydict['has_efield'] = str2bool(element.find("HAS_ELECTRIC_FIELD").text.strip()) + mydict['has_dipole_correction'] = str2bool(element.find("HAS_DIPOLE_CORRECTION").text.strip()) + mydict['efield_dir'] = int(element.find("FIELD_DIRECTION").text.strip()) + mydict['efield_maxpos'] = float(element.find("MAXIMUM_POSITION").text.strip()) + mydict['efield_invregion'] = float(element.find("INVERSE_REGION").text.strip()) + mydict['efield_amplitude'] = float(element.find("FIELD_AMPLITUDE").text.strip()) + + else: + + mydict['has_efield'] = False + mydict['has_dipole_correction'] = False + + mydict['got_efield'] = True + + +def get_spin_xml(tree, mydict): + """ """ + + if mydict.has_key('got_spin'): + if mydict['got_spin']: + return + + element = tree.find("SPIN") + mydict['lsda'] = str2bool(element.find("LSDA").text.strip()) + subel = element.find("NON-COLINEAR_CALCULATION") + if not subel is None: + mydict['noncolin'] = str2bool(subel.text.strip()) + else: + mydict['noncolin'] = False + + if mydict['lsda']: + spin = 2 + elif mydict['noncolin']: + spin = 4 + else: + spin = 1 + mydict['spin'] = spin + + if mydict['noncolin']: + mydict['npol'] = int(element.find("SPINOR_DIM").text.strip()) + else: + mydict['npol'] = 1 + + subel = element.find("SPIN-ORBIT_CALCULATION") + if not subel is None: + mydict['spin-orbit_calculation'] = str2bool(subel.text.strip()) + else: + mydict['spin-orbit_calculation'] = False + + subel = element.find("SPIN-ORBIT_DOMAG") + if not subel is None: + mydict['spin-orbit_domag'] = str2bool(subel.text.strip()) + else: + mydict['spin-orbit_domag'] = False + + mydict['got_spin'] = True + + +def get_magnetization_xml(tree, mydict): + """ """ + + if mydict.has_key('got_magnetization'): + if mydict['got_magnetization']: + return + + element = tree.find("MAGNETIZATION_INIT") + + mydict['constraint_mag'] = int(element.find("CONSTRAINT_MAG").text.strip()) + i_cons = mydict['constraint_mag'] + nsp = int(element.find("NUMBER_OF_SPECIES").text.strip()) + + mydict['mag_data'] = [] + mydict['mag_cons'] = [] + + for i in range(nsp): + specie = element.find("SPECIE."+str(i+1)) + + mag = {} + mag['starting_mag'] = float(specie.find("STARTING_MAGNETIZATION").text.strip()) + mag['angle1'] = float(specie.find("ANGLE1").text.strip()) * (3.141516/180.0) + mag['angle2'] = float(specie.find("ANGLE2").text.strip()) * (3.141516/180.0) + + if (i_cons == 1) or (i_cons == 2): + v = [] + v.append(float(specie.find("CONSTRAINT_1").text.strip())) + v.append(float(specie.find("CONSTRAINT_2").text.strip())) + v.append(float(specie.find("CONSTRAINT_3").text.strip())) + else: + v = None + + mydict['mag_data'].append(mag) + if not v is None: + mydict['mag_cons'].append(v) + + if i_cons == 3: + v = [] + v.append(float(specie.find("FIXED_MAGNETIZATION_1").text.strip())) + v.append(float(specie.find("FIXED_MAGNETIZATION_2").text.strip())) + v.append(float(specie.find("FIXED_MAGNETIZATION_3").text.strip())) + mydict['mag_cons'].append(v) + + if i_cons == 4: + v = [] + v.append(float(specie.find("MAGNETIC_FIELD_1").text.strip())) + v.append(float(specie.find("MAGNETIC_FIELD_2").text.strip())) + v.append(float(specie.find("MAGNETIC_FIELD_3").text.strip())) + mydict['bfield'] = v + else: + mydict['bfield'] = None + + mydict['two_fermi_energies'] = str2bool(element.find("TWO_FERMI_ENERGIES").text.strip()) + + if mydict['two_fermi_energies']: + subel = element.find("TWO_FERMI_ENERGIES") + v = float(subel.find("FIXED_MAGNETIZATION").text.strip()) + mydict['mag_cons'][0][2] = v + mydict['nelup'] = float(subel.find("ELECTRONS_UP").text.strip()) + mydict['neldw'] = float(subel.find("ELECTRONS_DOWN").text.strip()) + mydict['ef_up'] = float(subel.find("FERMI_ENERGY_UP").text.strip()) + mydict['ef_dw'] = float(subel.find("FERMI_ENERGY_DOWN").text.strip()) + + mydict['ef_up'] = mydict['ef_up'] * 2.0 + mydict['ef_dw'] = mydict['ef_dw'] * 2.0 + + if i_cons > 0: + mydict['lambda'] = float(element.find("LAMBDA").text.strip()) + + mydict['got_magnetization'] = True + + +def get_xc_xml(tree, mydict): + """ Exchange-Correlation """ + + if mydict.has_key('got_xc'): + if mydict['got_xc']: + return + + if not mydict['got_ions']: + get_ions_xml(tree, mydict) + + element = tree.find("EXCHANGE_CORRELATION") + + mydict['DFT'] = element.find("DFT").text + subel = element.find("LDA_PLUS_U_CALCULATION").text.strip() + + if subel == 'T': + + nsp = int(element.find("NUMBER_OF_SPECIES").text.strip()) + mydict['lda_plus_u_kind'] = float(element.find("LDA_PLUS_U_KIND").text.strip()) + mydict['hubbard_lmax'] = float(element.find("HUBBARD_LMAX").text.strip()) + mydict['hubbard_l'] = [float(x) for x in element.find("HUBBARD_L").text.split()] + mydict['hubbard_u'] = [float(x) for x in element.find("HUBBARD_U").text.split()] + mydict['hubbard_j'] = [float(x) for x in element.find("HUBBARD_J").text.split()] + mydict['hubbard_alpha'] = [float(x) for x in element.find("HUBBARD_ALPHA").text.split()] + + subel = element.find("NON_LOCAL_DF") + if not subel is None: + mydict['non-local_df'] = int(subel.text.strip()) + if mydict['non-local_df']==1 or mydict['non-local_df']== 2: + mydict['non-local_df'] = element.find("VDW_KERNEL_NAME").text + else: + mydict['non-local_df'] = 0 + + mydict['got_xc'] = True + + +def get_brillouin_zone_xml(tree, mydict): + """ """ + + if mydict.has_key('got_broullouin'): + if mydict['got_brillouin']: + return + + element = tree.find("BRILLOUIN_ZONE") + num_kpts = int(element.find("NUMBER_OF_K-POINTS").text.strip()) + nkstot = num_kpts + + if mydict['lsda']: + nkstot = nkstot * 2 + + mydict['nk'] = [int(x) for x in element.find("MONKHORST_PACK_GRID").attrib.values()] + mydict['k'] = [int(x) for x in element.find("MONKHORST_PACK_OFFSET").attrib.values()] + + mydict['xk'] = [] + mydict['wk'] = [] + + v = [] + w = [] + for i in range(num_kpts): + attr=element.find("K-POINT."+str(i+1)).attrib + v.append([float(x) for x in attr['XYZ'].split()]) + w.append(float(attr['WEIGHT'])) + + mydict['xk'] = v[:] + mydict['wk'] = w[:] + + if mydict['lsda']: + mydict['xk'].append(v[:]) + mydict['wk'].append(w[:]) + + if element.find("STARTING_K-POINTS") : + num_kpts_start = int(element.find("STARTING_K-POINTS").text.strip()) + mydict['xk_start'] = [] + mydict['wk_start'] = [] + for i in range(num_kpts_start): + element.find("K-POINT_START."+str(i+1)).attr + mydict['xk_start'].append([float(x) for x in attr['XYZ'].split()]) + mydict['wk_start'].append(float(attr['WEIGHT'])) + + # for completeness, Bravais-symmetries should also be read here // look in pw_restart.f90 from PW source code dir + + mydict['got_brillouin'] = True + + +def get_occupations_xml(tree, mydict): + """ """ + + if mydict.has_key('got_occupations'): + if mydict['got_occupations']: + return + + element = tree.find("OCCUPATIONS") + + subel = element.find("SMEARING_METHOD") + if not subel is None: + mydict['lgauss'] = str2bool(subel.text.strip()) + else: + mydict['lgauss'] = False + + if mydict['lgauss']: + mydict['ngauss'] = int(element.find("SMEARING_TYPE").text.strip()) + if mydict['ngauss'] == 0: + mydict['smearing'] = "gaussian" + if mydict['ngauss'] == 1: + mydict['smearing'] = "Methfessel-Paxton" + if mydict['ngauss'] == 2: + mydict['smearing'] = "Marzari-Vanderbilt" + if mydict['ngauss'] == 3: + mydict['smearing'] = "Fermi-Dirac" + + mydict['degauss'] = float(element.find("SMEARING_PARAMETER").text.strip()) + mydict['degauss'] = mydict['degauss'] * 2.0 + + else: + mydict['ngauss'] = 0 + mydict['degauss'] = 0.0 + + mydict['ltetra'] = str2bool(element.find("TETRAHEDRON_METHOD").text.strip()) + if mydict['ltetra']: + mydict['ntetra'] = int(element.find("NUMBER_OF_TETRAHEDRA").text.strip()) + ntetra = mydict['ntetra'] + mydict['tetrahedra'] = [] + for i in range (ntetra): + mydict['tetrahedra'].append([float(x) for x in element.find("TETRAHEDRON"+str(i+1)).text.split()]) + else: + mydict['ntetra'] = 0 + + subel = element.find("FIXED_OCCUPATIONS") + if not subel is None: + mydict['tfixed_occ'] = str2bool(subel.text.strip()) + else: + mydict['tfixed_occ'] = False + + if mydict['tfixed_occ']: + subel = element.find("INFO") + if not subel is None: + mydict['nupdwn'] = [subel.attr['nstates_up'], subel.attr['nstates_down']] + + else: + mydict['nupdwn'] = [mydict['nbnd'], mydict['nbnd']] + + mydict['finp'] = [float(x) for x in element.find("INPUT_OCC_UP").text.split()] + if mydict['lsda']: + mydict['finp'].append([float(x) for x in element.find("INPUT_OCC_DOWN").text.split()]) + + + mydict['ltetra'] = str2bool(element.find("TETRAHEDRON_METHOD").text.strip()) + + mydict['got_occupations'] = True + + +def get_band_structure_xml(tree, mydict): + """ """ + + if mydict.has_key('got_band_structure'): + if mydict['got_band_structure']: + return + + if not mydict['got_spin']: + get_spin_xml(tree, mydict) + if not mydict['got_brillouin']: + get_brillouin_zone_xml(tree, mydict) + + element = tree.find('BAND_STRUCTURE_INFO') + + mydict['nelec'] = int(float(element.find("NUMBER_OF_ELECTRONS").text.strip())) + subel = element.find("NUMBER_OF_ATOMIC_WFC") + if not subel is None: + mydict['natomwfc'] = int(element.find("NUMBER_OF_ATOMIC_WFC").text.strip()) + else: + mydict['natomwfc'] = 0 + + mydict['nbnd'] = int(element.find("NUMBER_OF_BANDS").text.strip()) + + subel = element.find("FERMI_ENERGY") + if not subel is None: + mydict['ef'] = float(subel.text.strip()) * 2 + else: + mydict['ef'] = 0 + + subel = element.find("TWO_FERMI_ENERGIES") + if not subel is None: + two_ef = str2bool(subel.text.strip()) + else: + two_ef = False + + if two_ef: + mydict['ef_up'] = float(elememt.find("FERMI_ENERGY_UP").text.strip()) * 2 + mydict['ef_dw'] = float(elememt.find("FERMI_ENERGY_DOWN").text.strip()) * 2 + + get_k_points_xml(tree, mydict) + + mydict['got_band_structure'] = True + mydict['got_ef'] = True + + +def get_fermi_energy_xml(tree, mydict): + """ + read in the fermi energy + """ + + if mydict.has_key('got_ef'): + if mydict['got_ef']: + return + + element = tree.find("BAND_STRUCTURE_INFO") + if not element.find("FERMI_ENERGY") is None: + mydict['ef'] = float(element.find("FERMI_ENERGY").text.strip()) * 2 + else: + mydict['ef'] = 0.0 + + mydict['got_ef'] = True + + +# ...................................................................................... + +def read_quantumespresso_xmloutput(filename, action, mydict=None): + """ + read the output text file from a QE run and extracts physical quantities + arguments: + - filename : that is the name of the xml output file you want to read in + - action : string: that specifies what exactly you want to read in. It basically + follows the convention from pw_restart.f90 + 'all' : read in all the data + 'ef' : read in just the fermi energy + 'kpts': read in only the k-points with eigenvalues and weights + 'config': read in the cell and atom parameters + ... : just check the lines below for further information + + the argument + returns a dictionary that contains the results. + """ + + call = {} + call['version'] = get_qexml_version + call['dim'] = get_dimensions_xml + call['cell'] = get_cell_xml + call['pw'] = get_planewaves_xml + call['ions'] = get_ions_xml + call['spin'] = get_spin_xml + call['init_mag'] = get_magnetization_xml + call['xc'] = get_xc_xml + call['occ'] = None + call['kpts'] = get_k_points_xml + call['bz'] = get_brillouin_zone_xml + call['bs'] = get_band_structure_xml + #call['wfc'] = get_wfc_xml + call['efield'] = get_efield_xml + call['symm'] = None + call['rho'] = None + call['pseudo'] = get_ions_xml + call['occupations'] = get_occupations_xml + + actions = {} + actions['dim'] = ['dim', 'bz'] + actions['pseudo'] = ['ions'] + actions['config'] = ['cell', 'ions'] + actions['rho'] = None + actions['wave'] = ['pw', 'wfc'] + actions['nowave'] = ['cell', 'pw','ions', 'spin', 'init_mag', 'xc', 'occ', 'bz', 'bs', 'symm', 'efield', 'occupations'] + actions['all'] = actions['nowave'].append('rho') + actions['ef'] = ['ef'] + actions['kpts'] = ['kpts'] + + if mydict is None: + mydict = {} + elif action == 'all': + mydict.clear() + + if action in actions.keys(): + + root = xml_openroot(filename) + mydict['filename']=filename + get_dimensions_xml(root, mydict) + get_qexml_version(root,mydict) + for A in actions[action]: + print A + if call[A]!=None : + call[A](root, mydict) + + else: + ValueError, "the action you specified ("+action+") is not present in my archive" + + return mydict + + + +# ------------------------------------------------------------------------------------------------------------------------------- +# +# read infile +# +# ------------------------------------------------------------------------------------------------------------------------------- + + +# ...................................................................................... + +def build_atoms(infilename, myprefix="dry_pwscf", verbose = False): + """ + this function builds an Atoms object in ASE starting + from a .in file, using the data collected by + read_in_file() + blocks must be the dictionary returned by read_in_file() + + it returns an Atoms object + """ + + new_infile = infilename+'.dry' + + blocks = read_in_file(infilename) + # changes the prefix in myprefix + if blocks.has_key('prefix'): + _command_string = "sed 's|prefix[ ]*=[ ]*[a-z/0-9]*|prefix = "+myprefix+",|' < "+infilename+" > "+new_infile + else: + _command_string = "sed 's|\&control|\&control\\n prefix = "+myprefix+",|' < "+infilename+" > "+new_infile + + print _command_string + os.system(_command_string) + + + if blocks['system']['ibrav'] == 0: + if verbose: + print "\t[write_atoms] ibrav = 0 found\n" + + if blocks['system'].has_key('alat'): + alat = blocks['system']['alat'] + elif blocks['system'].has_key('celldm(1)'): + alat = blocks['system']['celldm(1)'] + elif blocks['system'].has_key('a'): + alat = blocks['system']['a'] + else: + raise ValueError, "something got wrong: it seems that ibrav=0 but neither 'alat' nor 'celldm(1)' nor 'a' are present" + + # creates the cell as an array + cell = np.array(blocks['CELL_PARAMETERS']['cell']) + if verbose: + print "\t[write_atoms] found cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + # convert the cell in bohr units + # (note that alat or celldm(1) are supposed to be already in bohr) + if blocks['CELL_PARAMETERS']['attrib'] == 'alat' or blocks['CELL_PARAMETERS']['attrib'] == None: + cell = cell * alat + elif blocks['CELL_PARAMETERS']['attrib'] == 'angstrom': + cell = cell * convert('a','bohr') + if verbose: + print "\t[write_atoms] cell rescaled is:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + + else: + # it is needed to start qe in dry mode to obtains the + # cell parameters, because ibrav >0 + # QE will do this for us + + if verbose: + print "\t[write_atoms] ibrav > 0 found. Invoking QE\n" + + if 'ESPRESSO_ROOT' in os.environ: + rootdir = os.environ['ESPRESSO_ROOT'] + bindir = rootdir + '/PW/src/' + else: + rootdir= '$HOME/espresso/' + bindir = rootdir + '/PW/src/' + + execbin = "pw.x" + if not os.access(bindir+execbin, os.F_OK): + raise IOError, "binary %s does not exist" % (bindir+execbin) + if not os.access(bindir+execbin, os.X_OK): + raise IOError, "you do not have execution permission on the binary %s" % bindir+execbin + + # run a dry run: only the xml data file is written, with the information about the cell + tempfilename = myprefix+".EXIT" + fh_temp = open(tempfilename, "w") + fh_temp.close() + _commandstring = '%s < %s > %s' % ( bindir+execbin, new_infile, new_infile+'.out') + exitcode = os.system(_commandstring) + + # read in cell information + if verbose: + print "\t[write_atoms] now read-in the xml output\n" + root = ET.ElementTree(file=myprefix+'.save/data-file.xml').getroot() + mydict = {} + get_cell_xml(root, mydict) + + alat = mydict['alat'] + # the cell is already in bohr + cell = mydict['lattice_vector'] + + if verbose: + print "\t[write_atoms] found cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + # now actually creates the Atoms structure + + atoms = Atoms() + for A in blocks['ATOMIC_POSITIONS']['atoms']: + print "\t[write_atoms] adding atom: %s\n" % A['symbol'] + atoms.append(Atom(A['symbol'], tuple(A['pos']))) + print "\t[write_atoms] attaching cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + atoms.set_cell(cell, scale_atoms=True) + + return atoms + + +# ...................................................................................... + +def read_in_file(infilename): + """ + this function read a quantum espresso .in file + and returns a dictionary for all the namelists + and the cards found. + it correctly deals lines with more than one + comma-separated parameters + + it returns a dictionary, whos keys are the name + of the namelists and cards. + In turn, each value is a dictionary with all the + values found. + + as for the namecards: + (+) atomic_species is a dictionary whos keys are progeressive + integers and whose values are dictionaries with the following + keys: + 'symbol', 'attrib', 'mass', 'PP' + example: + {'1': {'symbol: 'H', 'attrib': 1, 'mass': 1.0008, 'PP': "pseudo_file_for_H"}, + '2': {'symbol: 'C', 'attrib': None, 'mass': 1, 'PP': "pseudo_file_for_C"}, + '3': {'symbol: 'H', 'attrib': 2, 'mass': 1.0008, 'PP': "different_pseudo_file_for_H"}, + ...} + the key 'attrib' is set in case there is more than 1 entry for a specie, for instance + to specify different PP: + ATOMIC_SPECIES + H1 1.0008 pseudo_file_for_H + H2 1.0008 different_pseudo_file_for_H + C 12 pseudo_file_for_C + + (+) atomic_positions is a dictionary whos keys are progressive + integers and whose values are dictionaries with the following keys: + + example: 'symbol', 'pos', 'if_pos' + {'1': {'symbol: 'H', 'pos': [1.0, 1.0, 1.0], 'if_pos': [0.1, 0.1, 0.1]}, + ...} + the key 'if_pos' may have value None if it wasn't present + + (+) all the other namecards are dictionaries with a unique key, 'elements', + that is a list of all the lines inside that card + + """ + + if not os.access(infilename, os.F_OK): + raise IOError, "the .in file %s is not present\n" % infilename + + fh = open(infilename) + alltext = fh.read() + alllines = alltext.split('\n') + + card_labels = ['ATOMIC_SPECIES', + 'ATOMIC_POSITIONS', + 'CELL_PARAMETERS', + 'K_POINTS', + 'CONSTRAINTS' + 'OCCUPATIONS'] + + blocks = {} + blocks['sparse']=[] + isnamelist = False + iscard = False + + for line in alllines: + + # determines whether we are inside a namelist or a card + if line.strip().startswith('&'): + isnamelist = True + blockname = line.strip()[1:] + blocks[blockname] = {} + + elif line.strip().startswith('/'): + isnamelist = False + + elif line.strip(): + if line.split()[0].strip() in card_labels: + iscard = True + + blockname = line.strip().split()[0] + blocks[blockname] = {} + + try: + attrib = line[line.find(line.strip().split()[1]):].strip() + if attrib.startswith('{'): + attrib = attrib[1:] + if attrib.endswith('}'): + attrib = attrib[:-1] + except: + attrib = None + blocks[blockname]["attrib"] = attrib + + else: # if in a namelist, isolate keywords and values + + if isnamelist: + tokens = line.split(',') + for t in tokens: + if t.strip(): + + key, svalue = t.strip().split('=') + + key = key.strip() + svalue = svalue.strip() + if svalue.endswith(','): + value = svalue[:-1].strip() + try: + value=int(svalue) + except ValueError: + try: + value=float(svalue) + except ValueError: + if svalue.lower() in ['.true.', 'true' , 't']: + value = True + elif svalue.lower() in ['.false.', 'false' , 'f']: + value = False + else: + value = str(svalue) + + blocks[blockname][key] = value + + elif iscard: + + if blockname == "ATOMIC_SPECIES": # -- SPECIES + tokens = line.split() + if(blocks[blockname].has_key('count')): + blocks[blockname]['count'] = blocks[blockname]['count']+1 + else: + blocks[blockname]['count'] = 1 + + symbol = tokens[0].strip() + + if symbol.find('-')> 0 or symbol.find('_')>0: + ssymbol = symbol.split('-') + if len(ssymbol) == 1: + ssymbol = symbol.split('_') + symbol=ssymbol[0] + symbol_attrib=ssymbol[1] + elif not symbol.isalpha(): + digits="0123456789" + found=min([symbol.index(d) for d in digits if d in symbol]) + symbol_attrib = int(symbol[found:]) + symbol = symbol[:found] + else: + symbol_attrib = None + + blocks[blockname][str(blocks[blockname]['count'])] = {'symbol': symbol, + 'attrib': symbol_attrib, + 'mass' : float(tokens[1]), + 'PP' : tokens[2].strip()} + + elif blockname == "ATOMIC_POSITIONS": # -- POSITIONS + tokens = line.split() + if(blocks[blockname].has_key('count')): + blocks[blockname]['count'] = blocks[blockname]['count']+1 + else: + blocks[blockname]['count'] = 1 + blocks[blockname]['atoms'] = [] + + idx = blocks[blockname]['count'] + if len(tokens) > 4: + if_pos = [float(eval(s)) for s in tokens[4:]] + else: + if_pos = None + + blocks[blockname]['atoms'].append({'symbol': tokens[0].strip(), + 'pos': [float(eval(s)) for s in tokens[1:4]], + 'if_pos': if_pos}) + + elif blockname == "CELL_PARAMETERS": # -- CELL PARAMETERS + + tokens = line.split() + + if not blocks[blockname].has_key('cell'): + blocks[blockname]['cell'] = [] + + blocks[blockname]['cell'].append([float(s) for s in tokens[0:3]]) + if(len(blocks[blockname]['cell'])) == 3: + iscard = False + + else: + if not blocks[blockname].has_key('elements'): + blocks[blockname]['elements'] = [] + blocks[blockname]['elements'].append(line) + + else: + blocks['sparse'].append(line) + + + fh.close() + return blocks diff --git a/qeutil/quantumespresso.py b/qeutil/quantumespresso.py new file mode 100644 index 0000000..7d3b41f --- /dev/null +++ b/qeutil/quantumespresso.py @@ -0,0 +1,1246 @@ +""" +This module defines an ASE interface to QUANTUM-ESPRESSO + +http://www.quantum-espresso.org +""" + +# the string +# ## [missed] +# tags points where I do have uncertainties + +import os +import subprocess +from glob import glob +from os.path import join, isfile, islink + +import numpy as np +import string + +from ase.data import chemical_symbols +from ase.data import atomic_numbers, atomic_masses +from ase.units import Bohr, Hartree, GPa + + +class QEpw: + """Class for doing QUANTUM-ESPRESSO calculations. + + The default parameters are very close to those that the QE + Fortran code would use. + + calc = QE(label='qe-pw', xc='scf', pulay=5, mix=0.1) + + Use the set_inp_X method to set extra INPUT parameters, where + X stands for (control | system | electrons | ions). For instance + + calc.set_inp_control('nstep', 30) + + """ + + __name__ = "QEpw" + __version__ = "1.0" + calculations_available = ['scf', 'nscf', 'bands', 'relax', 'md', 'vc-relax', 'vc-md'] + + def __init__(self, atoms=None, label='pw', xc='scf', nstep=50, etot_conv_thr=1.0e-4, #CONTROL + restart_mode=None, tstress=None, prefix='pwscf', + dt=None, pseudodir=None, + ibrav=0, cell_parameters_units=None, celldm=None, crystal = None,nbands=None, #SYSTEM + ecutwfc=None, ecutrho=None, occupations=None, + input_dft=None, smearing='gauss', degauss=None, nspin=None, + electron_maxstep=None, conv_thr=None, adaptive_thr=None, conv_thr_init=None, #ELECTRONS + conv_thr_multi=None, mixing_mode=None, mixing_beta=None, diag=None, + spoint=None, swfc=None, + cell_dynamics=None, press=None, wmass=None, press_conv_thr=None, #CELL + cell_dofree=None, + kpts=None, #K_POINTS + kpts_type=None, + cell_parameters=None): #CELL_PARAMETERS + + """Construct QuantumEspresso-calculator. + + Parameters + ========== + + ---------- CONTROL section + + label: str + Prefix to use for filenames (label.in, label.txt, ...). + Default is 'qe'. + xc: str + the operation QE is supposed to perform (scf, nscf, bands, relax, md, vc-relax, vc-md) + Default is 'scf'. + nstep: integer + number of ionic + electronic steps + Default is 1 if calc is 'scf', 'nscf', 'bands'; 50 for the other cases + etot_conv_thr: float + convergence threshold on total energy (a.u) for ionic minimization + Default is 1.0e-4 + + prefix: str + to be written in the input file + Default is 'pwscf' + outdir: str + input, temporary, output files are found in this directory + Default is value of the ESPRESSO_TMPDIR environment variable if set; current directory ('./') otherwise + pseudodir: str + directory containing pseudopotential files + Default is value of the $ESPRESSO_PSEUDO environment variable if set; '$HOME/espresso/pseudo/' otherwise + + ---------- SYSTEM section + + ibrav: integer + Bravais-lattice index (see QE manual for insights).Might be cell parameters, idx of BRAVET grid+ cell dimension, crystal param. + Default expectation are cell parameters, since you are supposed to inherit cell from atoms object + Required + units: string + either 'bohr' or 'angstrom', it is needed since ase does not handle units of measure when building an atomic system. + So, if ibrav = 0 and celldm(1) is given, it is given in Bohr and the CELL card is well-defined. Otherwise, we need to + specify the units (either Bohr or Angstrom) in which the Cell is given. + This variable is the way we do it. + Default value here is Bohr, in case alat is not given. + celldm(i), i=1,6: float + Crystallographic constants + must be passed as a list even for a single number. only those needed might be specified. + crystal (A, B, C, cosAB, cosAC, cosBC) : float + Traditional crystallographic constants + must be passed as a list with 6 entries + nat: integer + number of atoms in the unit cell + Default obtained from Atoms + ntyp: integer + number of types of atoms in the unit cell + Default obtained from Atoms + nbands: integer + number of electronic states (bands) to be calculated + Default (see QE documentation) + ecutwfc: float + kinetic energy cutoff (Ry) for wavefunctions + Required + ecutrho: float + kinetic energy cutoff (Ry) for charge density and potential + Default is 4 * ecutwfc + smearing: str + 'gaussian'/'gauss', 'methfessel-paxton'/'m-p'/'mp', 'marzari-vanderbilt'/'cold'/'m-v'/'mv', 'fermi-dirac'/'f-d'/'fd' + Default is 'gaussian' + input_dft: str + Exchange-correlation functional: eg 'PBE', 'BLYP', etc. Overrides the value read from pseudopotential files. + Default read from pseudopotential files + + ---------- ELECTRONS section + + electron_maxstep: int + maximum number of iterations in a scf step + default is 100 + conv_thr: float + conv. threshold for selconsistency + default is 1.0e-6 + adaptive_thr: logical + + conv_thr_init: float + if adaptive_thr is true, this is used for first cycle + default is 1.0e-3 + conv_thr_multi: float + if adaptive_thr is true, the convergence th for each cycle is given by min(conv_thr, conv_thr_multi * dexx) + default is 0.1 + mixing_mode: str + may be 'plain', 'TF', 'local-TF' + Default is 'plain' + mixing_beta: float + mixing factor for self-consistency + Default is 0.7 + diag: str + may be 'david', 'cg' + Default is 'david' + spoint: str + 'atomic', 'file' + Default for scf, *relax, *md is 'atomic'; for others the only possibility is 'file' + swfc: str + 'atomic', 'atomic+random', 'random', 'file' + Default is 'atomic+random' + + ---------- CELL section + present only if calculation is vc-relax or vc-md + + ---------- CELL PARAMETERS card + + cell_parameters: float + Optional card, needed only if ibrav = 0 is specified + made by 3 vectors + + cell_parameters_units: str + alat | bohr | angstrom + + + """ + + # initialize dictionaries for cards in input files + self.inp_control = {} + self.inp_control_writeall = {} + self.inp_system = {} + self.inp_system_writeall = {} + self.inp_electrons = {} + self.inp_electrons_writeall = {} + self.inp_ions = {} + self.inp_ions_writeall = {} + self.inp_cell = {} + self.inp_cell_writeall = {} + + # initialize key variables that must exist + self.error = False + self.error_message=[] + self.if_pos = None + self.PP = None + self.kpts =[] + self.kpts_num= 0 + self.nelect = 0 + self.alat = 0.0 + self.etotal = 0.0 + self.etotal_accuracy = 0.0 + self.dt = 0.0 + self.conv_thr=conv_thr + self.degauss=degauss + + + # | CONTROL input parameters + + self.label = label + if not xc.lower() in self.calculations_available: + raise ValueError, "The specified calculation (%s) is not available for this calculator" % xc.lower() + + self.calculation = xc.lower() + if( self.calculation in ['vc-relax', 'vc-md']) and cell_dynamics is None: + raise ValueError, 'your calculation requires you specify the cell-dynamic' + + self.nstep = nstep + self.etot_conv_thr = etot_conv_thr + + if not dt is None: + self.set_inp_control('dt', dt) + self.dt = dt + + if not restart_mode is None: + self.set_inp_control('restart_mode', restart_mode) + + if not tstress is None: + self.set_inp_control('tstress', tstress) + self.tstress = tstress + + if not prefix is None: # in case it is not given, default will be use by the code. + self.prefix = prefix + self.set_inp_control('prefix', prefix) + + # | SYSTEM input parameters + + self.set_ibrav_values() + if (ibrav is None): + # note that default value is 0, which means to copy the cell matrix from atoms structure + # so being None here might point to some problem + raise ValueError('Bravais-lattice index is required and not set') + if not ibrav in self.ibrav_allowed: + raise ValueError('ibrav given value is not recognizable as valid') + + self.ibrav = ibrav + self.set_inp_system('ibrav', ibrav) + + if cell_parameters_units is None: + if ((celldm is None) and (crystal is None)): + # in this case cell is not specified at all; actually, that might be the most common case, + # with ibrav=0 a 1)) or \ + ((not self.calculation in ["scf", "nscf", "bands"]) and (self.nstep != 50)): + fh.write(' nstep = %d,\n' % self.nstep) + + if len(self.inp_control.keys()) == 0: + raise NotImplementedError, "CONTROL section appears to be empty: that is not allowed" + + self.write_section_params(self.inp_control, self.inp_control_writeall, fh) + + fh.write(" pseudo_dir = '%s',\n" % self.pseudodir) + fh.write(" outdir = '%s',\n" % self.outdir) + + fh.write(' /\n') + + # ---------------------------------------------------------- + # | SYSTEM section + # ---------------------------------------------------------- + + if len(self.inp_system.keys()) == 0: + raise NotImplementedError, "SYSTEM section appears to be empty: that is not allowed" + + fh.write(' &SYSTEM\n') + self.write_section_params(self.inp_system, self.inp_system_writeall, fh) + + fh.write(' /\n') + + + # ---------------------------------------------------------- + # | ELECTRONS section + # ---------------------------------------------------------- + + fh.write(' &ELECTRONS\n') + self.write_section_params(self.inp_electrons, self.inp_electrons_writeall, fh) + + fh.write(' /\n') + + + # ---------------------------------------------------------- + # | IONS section + # ---------------------------------------------------------- + + fh.write(' &IONS\n') + self.write_section_params(self.inp_ions, self.inp_ions_writeall, fh) + + fh.write(' /\n') + + + # ---------------------------------------------------------- + # | CELL section + # ---------------------------------------------------------- + + if self.calculation in ['vc-relax', 'vc-md']: + fh.write('&CELL\n') + self.write_section_params(self.inp_cell, self.inp_cell_writeall, fh) + + fh.write('/\n') + + + # ---------------------------------------------------------- + # | Card: ATOMIC_SPECIES + # ---------------------------------------------------------- + + fh.write('ATOMIC_SPECIES\n') + + PP = self.PP[:] + PP_symbols = self.PP_symbols[:] + occurrences = {} + + for Z in self.species: + symbol = chemical_symbols[abs(Z)] + mass = atomic_masses[abs(Z)] + + if not occurrences.has_key(symbol): + occurrences[symbol] = 1 + else: + occurrences[symbol] += 1 + + if PP_symbols.count(symbol) == 0: + raise ValueError, "you did not specify PP file for element %s" % symbol + + idx = PP_symbols.index(symbol) + + # write the correct entry that is formed by SYM MASS PP-FILE-NAME + fh.write(' %s %f %s\n' % (symbol, mass, PP[idx][2])) + + # PP contains now the no-unique list of PP files + # make it a unique list + if(PP[idx][1] == occurrences[symbol]): + PP.remove(symbol) + PP_symbols.remove(symbol) + + + # ---------------------------------------------------------- + # | Card: ATOMIC_POSITIONS + # ---------------------------------------------------------- + + fh.write('ATOMIC_POSITIONS\n') + + if_pos = None + if not self.if_pos is None: + if_pos = self.if_pos[:] + if_pos_symbols = self.if_pos_symbols[:] + + occurrences = {} + + for Z, pos in zip(self.numbers, self.positions): + symbol = chemical_symbols[abs(Z)] + if not occurrences.has_key(symbol): + occurrences[symbol] = 1 + else: + occurrences[symbol] += 1 + mystring = ' '.join("%f " % f for f in pos) + fh.write(' %s %s' % (symbol, mystring)) + + if not if_pos is None: + if if_pos_symbols.count(symbol) > 0: + idx = if_pos_symbols.index(symbol) + if (if_pos[idx][1] == occurrences[symbol]) or (if_pos[idx][1] == 0): + mystring = ' '.join("%f " % f for f in if_pos[idx][2]) + fh.write(' %s' % mystring) + if(if_pos[idx][1] == occurrences[symbol]): + if_pos.remove(symbol) + if_pos_symbols.remove(symbol) + + fh.write('\n') + + + # ---------------------------------------------------------- + # | Card: K_POINTS + # ---------------------------------------------------------- + + fh.write('K_POINTS %s\n' % self.kpts_type) + if self.kpts_type is 'automatic': + fh.write(' %d %d %d %d %d %d\n' % tuple(self.kpts)) + elif not self.kpts_type is 'gamma': + fh.write(' %d\n' % self.kpts_num) + for array in self.kpts: + fh.write(' %.14f %.14f %.14f %.14f\n' % tuple(array)) + + + # ---------------------------------------------------------- + # | Card: CELL_PARAMETERS + # ---------------------------------------------------------- + + if self.ibrav == 0: + if (self.alat is None) and (self.cell_parameters_units.lower() is "alat"): + raise ValueError, "it seems you want to specify CELL PARAMETERS in alat units, but you did not specify alat" + + if not self.cell_parameters_units.lower() in ["alat", "bohr", "angstrom"]: + raise ValueError, "you specified un uknown type for CELL_PARAMETERS" + + fh.write('CELL_PARAMETERS {%s}\n' % self.cell_parameters_units) + + # Use self.cell ONLY IF ibrav == 0 AND cell_parameters have not been specified + if self.cell_parameters is None: + self.cell_parameters = self.cell + + if (self.cell_paramters.shape[0] != 3) or (self.cell_parameters.shape[1] != 3): + raise ValueError, "cell parameters must be 3x3 " + for i in range(3): + fh.write('%f %f %f\n' % tuple(self.cell_parameters[i])) + + + # ---------------------------------------------------------- + # | Card: OCCUPATIONS + # ---------------------------------------------------------- + + + if hasattr(self, 'occupations'): + if self.occupations.lower() is 'from_input': + fh.write('OCCUPATIONS\n') + + for i in range(self.nbands): + counter = 1 + while counter < 10: + fh.write('%.14f ' % self.occupations1[i]) + counter += 1 + + fh.write('\n') + + if self.nspin == 2: + for i in range(self.nbands): + counter = 1 + while counter < 10: + fh.write('%.14f ' % self.occupations2[i]) + counter += 1 + + fh.write('\n') + + + # ---------------------------------------------------------- + # | CLOSE IN FILE + # ---------------------------------------------------------- + + fh.close() + + + def write_section_params(self, inp, inp_writeall, fh): + """ Automatically writes all the specified parameters for this section. + Multidimensional parameters are taken into account properly. + """ + for key, value in inp.items(): + if value is None: + continue + + if isinstance(value, list): + if inp_writeall[key] == 1: + for element in value: + fh.write(' %s(%d) = %s' % (key, value.index(element), str(value))) + else: + for i in range(len(value)): + if inp_writeall[key][i] == 1: + fh.write(' %s(%d) = %s,\n' % (key, i+1, str(value[i]))) # i+1 instead of i because enumeration must start from 1 + + elif isinstance(value, bool): + fh.write(' %s = .%s.,\n' % (key, str(value).lower()) ) + elif isinstance(value, float): + fh.write(' %s = %g,\n' % (key, value)) + elif isinstance(value, int): + fh.write(' %s = %d,\n' % (key, value)) + elif isinstance(value, basestring): + fh.write(" %s = '%s',\n" % (key, value)) + +# END write_infile() + + + +# ============================================================================ +# : +# : Calculation methods +# : + + + + def update(self, atoms): # [Q] what are the conditions for performing a new calculation on the system? + if (not self.converged or # not yet calculated + len(self.numbers) != len(atoms) or # number of atoms has chenged + (self.numbers != atoms.get_atomic_numbers()).any()): # some atom has changed + self.initialize(atoms) + self.calculate(atoms) + elif ((self.positions != atoms.get_positions()).any() or # some atoms moved + (self.pbc != atoms.get_pbc()).any()) : # pbc has changed + # Not a good test self.cell and atoms.get_cell() are not properly handled yet + # (self.cell != atoms.get_cell().any())): # configuration has changed + self.calculate(atoms) + + def get_potential_energy(self, atoms, force_consistent=False): + self.update(atoms) + +# if force_consistent: +# return self.efree +# else: + # Energy extrapolated to zero Kelvin: + return (self.etotal) + + def get_potential_energy_accuracy(self, atoms, force_consistent=False): + return self.etotal_accuracy + + def get_forces(self, atoms): + self.update(atoms) + return self.forces.copy() + + def get_stress(self, atoms): + '''Returns stress tensor (3x3)''' + if (not self.tstress): + self.stress = True + self.update(atoms) + return -np.array(self.stress)* 1e-1 * GPa + + def calculate(self, atoms): + + self.write_infile() + + infilename = self.label + '.in' + outfilename = self.label + '.out' + #rename the output file if it does already exist + if islink(outfilename) or isfile(outfilename): + os.rename(outfilename, outfilename+'.bak') + + _commandstring = '%s < %s > %s' % ( self.bindir+self.execfile, infilename, outfilename) + exitcode = os.system(_commandstring) + + # if exitcode != 0: + # raise RuntimeError((self.execfile+' exited with exit code: %d. ' + + # 'Check %s.log for more information.') % + # (exitcode, self.label)) + + print "calculation done, read output.." + results = self.read_text_output() # read in results from output + #print "Keys obtained:", results.keys() + self.nelect = results['nelec'] + self.nbnd = results['nbands'] + self.kpts_num = results['kpts_num'] + self.xc = results['exchange_correlation'] + self.niter = results['niter'] + self.pressure = results['pressure'] + self.stress = results['stress'] + self.fermi_energy = results['fermi_energy'] + self.kpts = results['kpts'] + self.kpts_wk = results['kpts_wk'] + self.atoms_forces = results['atoms_forces'] + self.cell = results['cell'] + self.atomic_positions = results['atomic_positions'] + self.etotal = results['etotal'] + self.etotal_accuracy = results['etotal_accuracy'] + self.fermi_energy = results['fermi_energy'] + self.total_magnetization = results['total_magnetization'] + self.absolute_magnetization = results['absolute_magnetization'] + self.alat = results['alat'] + + + if results is None: + print "an error has occurred" + print "%s" % (''.join(s+'\n' for s in self.error_message)) + self.converged = False + else: + self.converged = True + + + +# ============================================================================ +# : +# : Read methods +# : + + + def read_text_output(self): + from ase.io.qe import read_quantumespresso_textoutput + filename = self.label + '.out' + return read_quantumespresso_textoutput(filename) + + + def read_xml_output(self): + from ase.io.qe import read_quantumespresso_xmloutput + filename = self.label + '.save/data-file.xml' + return read_quantumespresso_xmloutput(filename, "all") + + + def get_number_of_iterations(self): + return self.niter + + + def get_number_of_electrons(self): + return self.nelect + + + def set_atoms(self, atoms): + # atoms.pbc = [True, True, True] + self.atoms = atoms.copy() + + + def get_atoms(self): + atoms = self.atoms.copy() + # atoms.set_calculator(self) + return atoms + + + +# ===================================================================================================== +# routines that set up internal variables, permitted values, relationships among variables and so on +# ===================================================================================================== + + + def set_inp_control(self, key, value, write_all=1): + """Sets INPUT parameter for CONTROL section. + If the key is a list or a dictionary AND write_all is zero, + only the non-empty/non-zero values will be written in .in file + """ + self.inp_control[key] = value + if isinstance(value, list) or isinstance(value, dict): + self.inp_control_writeall[key] = write_all + + + def set_inp_system(self, key, value, write_all=1): + """Sets INPUT parameter for SYSTEM section. + If the key is a list or a dictionary AND write_all is zero, + only the non-empty/non-zero values will be written in .in file + """ + self.inp_system[key] = value + if isinstance(value, list) or isinstance(value, dict): + self.inp_system_writeall[key] = write_all + + + def set_inp_electrons(self, key, value, write_all=1): + """Sets INPUT parameter for ELECTRONS section. + If the key is a list or a dictionary AND write_all is zero, + only the non-empty/non-zero values will be written in .in file + """ + self.inp_electrons[key] = value + if isinstance(value, list) or isinstance(value, dict): + self.inp_electrons_writeall[key] = write_all + + + def set_inp_ions(self, key, value, write_all=1): + """Sets INPUT parameter for IONS section. + If the key is a list or a dictionary AND write_all is zero, + only the non-empty/non-zero values will be written in .in file + """ + self.inp_ions[key] = value + if isinstance(value, list) or isinstance(value, dict): + self.inp_ions_writeall[key] = write_all + + + def set_inp_cell(self, key, value, write_all=1): + """Sets INPUT parameter for CELL section. + If the key is a list or a dictionary AND write_all is zero, + only the non-empty/non-zero values will be written in .in file + """ + self.inp_cell[key] = value + if isinstance(value, list) or isinstance(value, dict): + self.inp_cell_writeall[key] = write_all + + + def set_if_pos(self, if_pos): + """Sets the values for the if_pos arrays in the ATOMIC_POSITIONS CARD. + if_pos is meant to be a list of tuples, having an entry for each atoms you + want to put a force on. + Each tuple is formed as + [Symbol, occurrence, force] + where 'Symbol' is the chemical symbol of the atom you are referring to, 'occurrence' + is the occurrence of that atoms, 'force' is 3-component list of integers that + actually represents the force. + + note: the occurrence is meant to be in fortran-style, i.e. to start from 1 not from 0. + A 0 value means "all occurrences" + + """ + if isinstance(if_pos, list): + # check that if_pos object is well-formed + for el in if_pos: + if (isinstance(el[0], str) and isinstance(el[1], int) and isinstance(el[2], list)): + for subel in el[2]: + if not isinstance(el[2][subel], int): + raise TypeError, "if_pos object is malformed" + else: + raise TypeError, "if_pos object is malformed" + + # sort the list by chemical symbol as primary key and occurrence as secondary key, so + # that the imposed forces may be retrieved in order of appearence of elements + if_pos.sort(key=lambda l: (l[0], l[1])) + self.if_pos = if_pos[:] + self.if_pos_symbols = [] + for el in self.if_pos: + self.if_pos_symbols.append[if_pos[el][0]] + + else: + raise TypeError, "if_pos object is malformed" + + + def set_PP(self, PP): + """Sets the values for the PseudoPotential file names in the ATOMIC_SPECIES CARD. + PP is meant to be a list of tuples, having an entry for each atom (or atom specie, + see below) is in the system. + Each tuple is formed as + [Symbol, occurrence, filename] + where 'Symbol' is the chemical symbol of the atom you are referring to, 'occurrence' + is the occurrence of that atoms, 'filename' is a string that contains the file name + of the pseudopotential file + + note: the occurrence is meant to be in fortran-style, i.e. to start from 1 not from 0 + A 0 value means "all occurrences" + """ + if isinstance(PP, list): + # check that PP object is well-formed + for el in PP: + if (not isinstance(el[0], str)) or (not isinstance(el[1], int)) or (not isinstance(el[2], str)): + raise TypeError, "PP object is malformed" + + # sort the list by chemical symbol as primary key and occurrence as secondary key, so + # that the pseudo potential may be retrieved in order of appearence of elements + PP.sort(key=lambda l: (l[0], l[1])) + self.PP = PP[:] + self.PP_symbols = [] + for el in PP: + self.PP_symbols.append(el[0]) + else: + raise TypeError, "PP object is malformed" + + + def set_ibrav_values(self): + """build the list of allowed values for ibrav + build the dictionary of what entries are expected to be specified for the celldm() + array as a function of ibrav value + """ + # build the list of permitted values for ibrav + self.ibrav_allowed = range(15) + others=[-5, -9, -12] + self.ibrav_allowed.append(others) + + # build the 'expectation list' of values for celldm given an ibrav value + self.celldm_expected_values ={0: [1,0,0,0,0,0], + 1: [1,0,0,0,0,0], + 2: [1,0,0,0,0,0], + 3: [1,0,0,0,0,0], + 4: [1,0,1,0,0,0], + 5: [1,0,0,1,0,0], + -5: [1,0,0,1,0,0], + 6: [1,0,1,0,0,0], + 7: [1,0,1,0,0,0], + 8: [1,1,1,0,0,0], + 9: [1,1,1,0,0,0], + -9: [1,1,1,0,0,0], + 10: [1,1,1,0,0,0], + 11: [1,1,1,0,0,0], + 12: [1,1,1,1,0,0], + -12: [1,1,1,0,1,0], + 13: [1,1,1,1,0,0], + 14: [1,1,1,1,1,1]} + + def set_celldm(self, celldm, crystal): + """check that the given celldm array is consistent with the specified ibrav value and + assign its the values + note that celldm is an array that contains only the value you need to assign. For + instance, let's say ibrav=5: then you need to set only the first and the fourth entries + and the celldm list that you pass to the calculator might be a 2-entries list and + does not need to be a 6-entries list with 4 zero entries. + Of course you may pass as well a full 6-entries list. + If crystal is given, uses crystal. + """ + if (celldm is None) and (not crystal is None): + self.celldm = None + self.crystal = crystal[:] + # adds input parameters to the system section for the purpose of automaticly writing them + self.set_inp_system('A', self.crystal[0]) + self.set_inp_system('B', self.crystal[1]) + self.set_inp_system('C', self.crystal[2]) + self.set_inp_system('cosAB', self.crystal[3]) + self.set_inp_system('cosAC', self.crystal[4]) + self.set_inp_system('cosBC', self.crystal[5]) + self.alat = self.crystal[0] + + else: + # checks whether celldm is a list + if not isinstance(celldm, list): + if not isinstance(celldm, float): + raise TypeError, "celldm must be a list of floats or at least a single float" + # if it is a float, makes it a one-entry list + else: + celldm = [celldm] + + # checks whether celldm has as many entries as it is expected accordingly with the ibrav value + if len(np.nonzero(celldm)[0]) != self.celldm_expected_values[self.ibrav].count(1) : + raise ValueError, "the number specified entries for celldm (%d) is different than expected for the given ibrav" + + # now insert the values at the right positions + self.celldm = [0, 0, 0, 0, 0, 0] + ctrl = self.celldm_expected_values[self.ibrav] + idxs = [i for i in range(6) if ctrl[i]==1] + + for i in range(len(idxs)): + self.celldm[idxs[i]]= celldm[i] + + self.set_inp_system('celldm', self.celldm, self.celldm_expected_values[self.ibrav]) + self.alat = self.celldm[0] + + # note: celldm values will be automatically written as "celldm(i) = value" for all, and only, the needed i + + + def check_kpts_type(self, kpts_type): + """Checks whether the given type is among the allowed ones""" + + if kpts_type.lower() in ['gamma', 'automatic', 'tpiba', 'crystal', 'tpiba_b', 'crystal_b', 'tpiba_c', 'crystal_c']: + return True + else: + return False + + + def set_kpts(self, kpts=None, kpts_type=None): + """Sets the kpts accordingly with kpts_type """ + + if not kpts_type is None: + # if kpts_type is given and is valid, it overrides self.kpts_type + if self.check_kpts_type(kpts_type): + self.kpts_type = kpts_type + else: + # otherwise, check whether it already exists in self + if self.kpts_type is None: + raise ValueError, "no kpts_type has been defined" + else: + kpts_type = self.kpts_type + + if kpts_type.lower() == 'gamma': + self.kpts = None + + elif kpts_type.lower() == "automatic": + if isinstance(kpts, list): + if (len(kpts) == 6): + self.kpts = kpts[:] + else: + raise TypeError, '6 values expected for kpts with \'automatic\' flag' + + else: + raise TypeError, "wrong type for kpts" + + else: + entries = 0; + if isinstance(kpts, list): + kpts_num = kpts[0] + self.kpts = []; + for el in range(len(kpts)): + if el > 0: + if isinstance(kpts[el], list) and (len(kpts[el]) == 4): + self.kpts.append(kpts[el]) + entries += 1 + + if entries != kpts_num: + raise ValueError, "the specified kpoints are malformed, %d found vs %d expected" % (entries, kpts_num) + + self.kpts_num = kpts_num + + def set_occupations(self, occ1, occ2=None): + """sets the occupations array""" + + if not self.nbands is None: + if len(occ1) == self.nbands: + self.occupations1 = occ1[:] + if (not occ2 is None) and (self.nspin == 2) and (len(occ2) == self.nbands): + self.occupations2 = occ2[:] + else: + raise ValueError, "impossibile to set up occupations, nbands has not been specified yet" + + + + +# ===================================================================================================== +# POST - PROCESSING DRIVING ROUTINES +# ===================================================================================================== + + def PP(self, plot_num, input_args, plot_args): + """ + """ + + # specifies what parameters must be present in input_args, depending on the value of plot_num + input_deps = { '0' : ['spin_component'], + '1' : ['spin_component'], + '5' : ['sample_bias'], + '7' : ['kpoint', 'kband', 'lsign', 'spin_component'], + '10' : ['emin', 'emax', 'spin_component'], + '13' : ['spin_component'], + '17' : ['spin_component'] } + + # specifies what parameters must be present in plot_args, depending on the value of iflag + # iflag is an entry of the plot_args dictionary + plot_deps = { '0' : ['e1', 'x0', 'nx'], + '1' : ['e1', 'x0', 'nx'], + '2' : ['e1', 'e2', 'x0', 'nx', 'ny'], + '3' : ['e1', 'e2', 'e3', 'x0', 'nx,', 'ny', 'nz'] } + + + + fh = open('inputpp.in', 'w') + + fh.write(' &INPUTPP\n') + fh.write(' prefix = %s,\n' % self.prefix) + fh.write(' outdir = %s,\n' % self.outdir) + if not input_args_has_key('filename'): + fh.write(' filplot = filplot,\n') + else: + fh.write(' filplot = %s,\n' % input_args['filename']) + fh.write(' plot_num = %s,\n' % str(plot_num)) + + for D in input_deps[str(plot_num)]: + fh.write(' %s = %s,\n' % (D, str(input_args[D]))) + + fh.write(' /\n') + + fh.write(' &PLOT\n') + + fh.write(' nfile = %s,\n' % str(plot_args['nfiles'])) + + if plot_args['nfiles'] > 1: + if not plot_args.has_key('filenames'): + raise ValueError, "for more than 1 input file you must specify individual filename through plot_args" + + for i in range(plot_args['nfiles']): + fh.write(' filepp('+str(i+1)+') = %s,\n' % plot_args['filenames'][i]) + else: + if plot_args.has_key('filenames'): + fh.write(' filepp(1) = %s,\n' % plot_args['filenames']) + else: + fh.write(' filepp(1) = filplot,\n') + + if plot_args.has_key('weights'): + for i in range(plot_args['nfiles']): + fh.write(' weight('+str(i+1)+') = %s,\n' % str(plot_args['weights'][i])) + + + fh.write(' iflag = %s,\n' % str(plot_args['iflag'])) + fh.write(' output_format = %s,\n' % str(plot_args['formats'])) + fh.write(' fileout = %s,\n' % str(plot_args['output'])) + + for D in plot_deps[str(plot_args['iflag'])]: + + value = plot_args[D] + if isinstance(value, list): + for el in value: + fh.write(' %s(%d) = %s,\n' % (D, value.index(el) ,str(value))) + else: + fh.write(' %s = %s,\n' % (D, str(value))) + + fh.write(' /\n') + execbin = "pp.x" + if 'ESPRESSO_ROOT' in os.environ: + rootdir = os.environ['ESPRESSO_ROOT'] + bindir = rootdir + '/PP/src/' + else: + rootdir= '$HOME/espresso/' + bindir = rootdir + '/PP/src/' + + if not os.access(bindir+execbin, os.F_OK): + raise IOError, "binary %s does not exist" % (bindir+execbin) + if not os.access(bindir+execbin, os.X_OK): + raise IOError, "you do not have execution permission on the binary %s" % bindir+execbin + + infilename='inputpp.in' + outfilename='outputpp' + _commandstring = '%s < %s > %s' % ( bindir+execfile, infilename, outfilename) + exitcode = os.system(_commandstring) + + + + + +# conversions among energy units +# all units are given in eV +units_energy = {'ev': 1, # ev + 'hr': 27.21138505, # hartree + 'ry': 13.605692525, # rydberg + 'j': 6.24150934731e+18} # joule + +# conversions among length units +# all units are given in bohr +units_length = {'bohr': 1, + 'a': 1.89035916824, # angstrom + 'm': 1.89035916824e10} # meter + +units = {'ev': units_energy, + 'hr': units_energy, + 'ry': units_energy, + 'j' : units_energy, + 'bohr': units_length, + 'a': units_length, + 'm': units_length} + +def convert(v1, v2): + try: + isinstance(v1, str) & isinstance(v2, str) + except TypeError: + print "convert argument must be strings" + myv1=v1.lower() + myv2=v2.lower() + if (v1 in units.keys()) & (v2 in units.keys()): + try: + units[v1] == units[v2] + except ValueError: + print "units dimensions must be the same for conversion" + + return (units[v1])[v1] / (units[v2])[v2] + else: + return -1 diff --git a/qeutil/readers.py b/qeutil/readers.py new file mode 100644 index 0000000..7598d32 --- /dev/null +++ b/qeutil/readers.py @@ -0,0 +1,472 @@ +def read_eigenvect(fn): + '''Read the normalized eigenvectors/sqrt(mass)=displacement vectors + from the ph.x output file. This is different, but similar to the matdyn.modes file. + Returns a list of lists: + [ + [q, [[omega, ev], [omega, ev], ...]], + [q, [[omega, ev], [omega, ev], ...]] + ... + ] + where ev is an #atomsx3 complex array of eigenvectors for a given mode with frequency omega. + The indexes in ev are [atomic,cartesian]. + ''' + + evl=[] + start=False + for ln in open(fn).readlines(): + ln=ln.split() + if len(ln)==0: + # skip empty lines + continue + if ln[0].find('iagonalizing')==1 : + # We are in the eigenvectors block + start=True + if start and ln[0]=='q' : + # read the q-vector + if len(ln)==5 : + # we are reading the matdyn.modes file + q=array(ln[2:],dtype=float) + else : + # we are reading the fildyn file + q=array(ln[3:6],dtype=float) + ql=[] + evl.append(ql) + ql.append(q) + modes=[] + ql.append(modes) + if start and ln[0].find('omega')==0 : + # read the frequency of the mode + o=float(ln[-5]) + mode=[] + modes.append(mode) + mode.append(o) + if start and ln[0].find('(')==0 : + # read the atomic displacements line by line converting to complex on the flight + mode.append(array(ln[1:7:2],dtype=float)+1j*array(ln[2:7:2],dtype=float)) + + return [[q[0], [[m[0],array(m[1:])] for m in q[1]]] for q in evl] + + +def read_D2(fn='matdyn', wd='.'): + '''Read the dynamical matrix sampling over the B.Z. from the matdyn files. + Input + ----- + fn - base file name of the dynamical matrix, *without* the zero at the end. + wd - working directory + + Output + ------ + Dynamical matrix of the crystal sampled over q-points in the B.Z. as a list. + D2[na, nb, i, j] is a nat x nat x 3 x 3 dynamical matrix at the q point in B.Z. + nat - number of atoms: + + [[q, D2[na, nb, i, j]], + [q, D2[na, nb, i, j]], + ... + ] + ''' + + # Read the description file + + qvect=loadtxt(os.path.join(wd,fn+'0'),skiprows=2) + qlatt=array(open(os.path.join(wd,fn+'0')).readline().split(),dtype=int) + #print "Q-lattice: ", qlatt, ',', len(qvect), " irreducible q-vectors:" + #print qvect + + indm=False + D2=[] + for k in range(1,len(qvect)+1): + # Collect the matrices for each q-vect set + inhd=True + lnum=0 + nat=0 + for ln in open(os.path.join(wd,fn+("%d" % (k,)))): + fld=ln.split() + lnum+=1 + if inhd and lnum==3 : + nat=int(fld[1]) + if ln.find('Dynamical Matrix in cartesian axes') > -1 : + indm=True + inhd=False + if ln.find('Dielectric Tensor:') > -1 : + indm=False + if ln.find('Effective Charges') > -1 : + indm=False + if ln.find('Diagonalizing the dynamical matrix') > -1 : + indm=False + if indm and ln.find('q = (')>-1 : + q=[] + q.append(array(fld[-4:-1],dtype=float)) + D2.append(q) + if indm and len(fld)==2 : + s_sp=array(fld,dtype=int)-1 + if indm and len(fld)==6 : + dxyz=array(fld[::2],dtype=float)+1j*array(fld[1::2],dtype=float) + q.append(dxyz) + #print dxyz + # Re-process all D2 matrices into nat x nat x 3 x 3 matrices and put + return [[ql[0],array(ql[1:]).reshape(nat,nat,3,3)] for ql in D2] + +def read_D3(fn='d3dyn',wd="."): + '''Reads d3dyn file from QE d3.x program containing D3 matrix of PRB 87 (2013) 214303. + Returns q-vectors and re-ordered complex D3 matrix. + The returned d3 matrix has following indexing (left to right): + q, index of q-vector in qvect array + i, cart. index for atom a + j, cart. index for atom b + k, cart. index for atom c + na, index of atom a + nb, index of atom b + nc, index of atom c + and shape (#q, 3, 3, 3, #atoms, #atoms, #atoms) + ''' + fd3dyn=open(wd+'/'+fn) + fd3dyn=[ln.split() for ln in fd3dyn] + nat=int(fd3dyn[2][1]) + qvect=[] + head=0 + for ln in fd3dyn: + if not qvect: + head+=1 + if len(ln)>2 and ln[0]=='q' and ln[1]=='=': + qvect.append(array([float(x) for x in ln[-4:-1]])) + modes=[] + for ln in fd3dyn[head+1:]: + if len(ln)==3: + modes.append([float(x) for x in ln]) + qvect=array(qvect) + modes=array(modes) + modes=modes.reshape((len(qvect),nat,3,nat,nat,3,3,2)) + # collect real and imaginary parts into complex numbers + modes=modes[...,0]+1j*modes[...,1] + # Account for ordering of d3.x output. + # The d3 matrix has following indexing: + # d3( + # q, index of q-vector in qvect array + # i, cart. index for atom a + # j, cart. index for atom b + # k, cart. index for atom c + # na, index of atom a + # nb, index of atom b + # nc, index of atom c + # if you just read in the numbers the ordering will be + # (slowest to fastest index): + # q, na, i, nb, nc, j, k, (re/im) + d3=modes.transpose(0,2,5,6,1,3,4) + return qvect, d3 + + + +# ------------------------------------------------------------------------------------------------------------------------------- +# +# read infile +# +# ------------------------------------------------------------------------------------------------------------------------------- + + +# ...................................................................................... + +def build_atoms(infilename, myprefix="dry_pwscf", verbose = False): + """ + this function builds an Atoms object in ASE starting + from a .in file, using the data collected by + read_in_file() + blocks must be the dictionary returned by read_in_file() + + it returns an Atoms object + """ + + new_infile = infilename+'.dry' + + blocks = read_in_file(infilename) + # changes the prefix in myprefix + if blocks.has_key('prefix'): + _command_string = "sed 's|prefix[ ]*=[ ]*[a-z/0-9]*|prefix = "+myprefix+",|' < "+infilename+" > "+new_infile + else: + _command_string = "sed 's|\&control|\&control\\n prefix = "+myprefix+",|' < "+infilename+" > "+new_infile + + print _command_string + os.system(_command_string) + + + if blocks['system']['ibrav'] == 0: + if verbose: + print "\t[write_atoms] ibrav = 0 found\n" + + if blocks['system'].has_key('alat'): + alat = blocks['system']['alat'] + elif blocks['system'].has_key('celldm(1)'): + alat = blocks['system']['celldm(1)'] + elif blocks['system'].has_key('a'): + alat = blocks['system']['a'] + else: + raise ValueError, "something got wrong: it seems that ibrav=0 but neither 'alat' nor 'celldm(1)' nor 'a' are present" + + # creates the cell as an array + cell = np.array(blocks['CELL_PARAMETERS']['cell']) + if verbose: + print "\t[write_atoms] found cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + # convert the cell in bohr units + # (note that alat or celldm(1) are supposed to be already in bohr) + if blocks['CELL_PARAMETERS']['attrib'] == 'alat' or blocks['CELL_PARAMETERS']['attrib'] == None: + cell = cell * alat + elif blocks['CELL_PARAMETERS']['attrib'] == 'angstrom': + cell = cell * convert('a','bohr') + if verbose: + print "\t[write_atoms] cell rescaled is:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + + else: + # it is needed to start qe in dry mode to obtains the + # cell parameters, because ibrav >0 + # QE will do this for us + + if verbose: + print "\t[write_atoms] ibrav > 0 found. Invoking QE\n" + + if 'ESPRESSO_ROOT' in os.environ: + rootdir = os.environ['ESPRESSO_ROOT'] + bindir = rootdir + '/PW/src/' + else: + rootdir= '$HOME/espresso/' + bindir = rootdir + '/PW/src/' + + execbin = "pw.x" + if not os.access(bindir+execbin, os.F_OK): + raise IOError, "binary %s does not exist" % (bindir+execbin) + if not os.access(bindir+execbin, os.X_OK): + raise IOError, "you do not have execution permission on the binary %s" % bindir+execbin + + # run a dry run: only the xml data file is written, with the information about the cell + tempfilename = myprefix+".EXIT" + fh_temp = open(tempfilename, "w") + fh_temp.close() + _commandstring = '%s < %s > %s' % ( bindir+execbin, new_infile, new_infile+'.out') + exitcode = os.system(_commandstring) + + # read in cell information + if verbose: + print "\t[write_atoms] now read-in the xml output\n" + root = ET.ElementTree(file=myprefix+'.save/data-file.xml').getroot() + mydict = {} + get_cell_xml(root, mydict) + + alat = mydict['alat'] + # the cell is already in bohr + cell = mydict['lattice_vector'] + + if verbose: + print "\t[write_atoms] found cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + # now actually creates the Atoms structure + + atoms = Atoms() + for A in blocks['ATOMIC_POSITIONS']['atoms']: + print "\t[write_atoms] adding atom: %s\n" % A['symbol'] + atoms.append(Atom(A['symbol'], tuple(A['pos']))) + print "\t[write_atoms] attaching cell:\n" + for R in cell: + print "\t\t %f %f %f" % tuple(R) + + atoms.set_cell(cell, scale_atoms=True) + + return atoms + + +# ...................................................................................... + +def read_in_file(infilename): + """ + this function read a quantum espresso .in file + and returns a dictionary for all the namelists + and the cards found. + it correctly deals lines with more than one + comma-separated parameters + + it returns a dictionary, whos keys are the name + of the namelists and cards. + In turn, each value is a dictionary with all the + values found. + + as for the namecards: + (+) atomic_species is a dictionary whos keys are progeressive + integers and whose values are dictionaries with the following + keys: + 'symbol', 'attrib', 'mass', 'PP' + example: + {'1': {'symbol: 'H', 'attrib': 1, 'mass': 1.0008, 'PP': "pseudo_file_for_H"}, + '2': {'symbol: 'C', 'attrib': None, 'mass': 1, 'PP': "pseudo_file_for_C"}, + '3': {'symbol: 'H', 'attrib': 2, 'mass': 1.0008, 'PP': "different_pseudo_file_for_H"}, + ...} + the key 'attrib' is set in case there is more than 1 entry for a specie, for instance + to specify different PP: + ATOMIC_SPECIES + H1 1.0008 pseudo_file_for_H + H2 1.0008 different_pseudo_file_for_H + C 12 pseudo_file_for_C + + (+) atomic_positions is a dictionary whos keys are progressive + integers and whose values are dictionaries with the following keys: + + example: 'symbol', 'pos', 'if_pos' + {'1': {'symbol: 'H', 'pos': [1.0, 1.0, 1.0], 'if_pos': [0.1, 0.1, 0.1]}, + ...} + the key 'if_pos' may have value None if it wasn't present + + (+) all the other namecards are dictionaries with a unique key, 'elements', + that is a list of all the lines inside that card + + """ + + if not os.access(infilename, os.F_OK): + raise IOError, "the .in file %s is not present\n" % infilename + + fh = open(infilename) + alltext = fh.read() + alllines = alltext.split('\n') + + card_labels = ['ATOMIC_SPECIES', + 'ATOMIC_POSITIONS', + 'CELL_PARAMETERS', + 'K_POINTS', + 'CONSTRAINTS' + 'OCCUPATIONS'] + + blocks = {} + blocks['sparse']=[] + isnamelist = False + iscard = False + + for line in alllines: + + # determines whether we are inside a namelist or a card + if line.strip().startswith('&'): + isnamelist = True + blockname = line.strip()[1:] + blocks[blockname] = {} + + elif line.strip().startswith('/'): + isnamelist = False + + elif line.strip(): + if line.split()[0].strip() in card_labels: + iscard = True + + blockname = line.strip().split()[0] + blocks[blockname] = {} + + try: + attrib = line[line.find(line.strip().split()[1]):].strip() + if attrib.startswith('{'): + attrib = attrib[1:] + if attrib.endswith('}'): + attrib = attrib[:-1] + except: + attrib = None + blocks[blockname]["attrib"] = attrib + + else: # if in a namelist, isolate keywords and values + + if isnamelist: + tokens = line.split(',') + for t in tokens: + if t.strip(): + + key, svalue = t.strip().split('=') + + key = key.strip() + svalue = svalue.strip() + if svalue.endswith(','): + value = svalue[:-1].strip() + try: + value=int(svalue) + except ValueError: + try: + value=float(svalue) + except ValueError: + if svalue.lower() in ['.true.', 'true' , 't']: + value = True + elif svalue.lower() in ['.false.', 'false' , 'f']: + value = False + else: + value = str(svalue) + + blocks[blockname][key] = value + + elif iscard: + + if blockname == "ATOMIC_SPECIES": # -- SPECIES + tokens = line.split() + if(blocks[blockname].has_key('count')): + blocks[blockname]['count'] = blocks[blockname]['count']+1 + else: + blocks[blockname]['count'] = 1 + + symbol = tokens[0].strip() + + if symbol.find('-')> 0 or symbol.find('_')>0: + ssymbol = symbol.split('-') + if len(ssymbol) == 1: + ssymbol = symbol.split('_') + symbol=ssymbol[0] + symbol_attrib=ssymbol[1] + elif not symbol.isalpha(): + digits="0123456789" + found=min([symbol.index(d) for d in digits if d in symbol]) + symbol_attrib = int(symbol[found:]) + symbol = symbol[:found] + else: + symbol_attrib = None + + blocks[blockname][str(blocks[blockname]['count'])] = {'symbol': symbol, + 'attrib': symbol_attrib, + 'mass' : float(tokens[1]), + 'PP' : tokens[2].strip()} + + elif blockname == "ATOMIC_POSITIONS": # -- POSITIONS + tokens = line.split() + if(blocks[blockname].has_key('count')): + blocks[blockname]['count'] = blocks[blockname]['count']+1 + else: + blocks[blockname]['count'] = 1 + blocks[blockname]['atoms'] = [] + + idx = blocks[blockname]['count'] + if len(tokens) > 4: + if_pos = [float(eval(s)) for s in tokens[4:]] + else: + if_pos = None + + blocks[blockname]['atoms'].append({'symbol': tokens[0].strip(), + 'pos': [float(eval(s)) for s in tokens[1:4]], + 'if_pos': if_pos}) + + elif blockname == "CELL_PARAMETERS": # -- CELL PARAMETERS + + tokens = line.split() + + if not blocks[blockname].has_key('cell'): + blocks[blockname]['cell'] = [] + + blocks[blockname]['cell'].append([float(s) for s in tokens[0:3]]) + if(len(blocks[blockname]['cell'])) == 3: + iscard = False + + else: + if not blocks[blockname].has_key('elements'): + blocks[blockname]['elements'] = [] + blocks[blockname]['elements'].append(line) + + else: + blocks['sparse'].append(line) + + + fh.close() + return blocks diff --git a/qeutil/writers.py b/qeutil/writers.py new file mode 100644 index 0000000..eb30b52 --- /dev/null +++ b/qeutil/writers.py @@ -0,0 +1,124 @@ +# PWscf input file +pw_in=''' +&CONTROL + calculation = '%(calc)s', + prefix = '%(prefix)s', + tstress = .true., + pseudo_dir = '../pspot', + outdir = './%(outdir)s/', +/ +&SYSTEM + A = %(A)f, + ecutwfc = %(ecut)f, + ibrav = 2, + nat = 3, + ntyp = 2, +/ +&ELECTRONS +/ +ATOMIC_SPECIES + Th 232.038100 Th_%(XC)s_nc.ncpp + O 15.999400 O_%(XC)s_nc.ncpp +ATOMIC_POSITIONS + Th 0.000000 0.000000 0.000000 + O 0.250000 0.250000 0.250000 + O -0.250000 -0.250000 -0.250000 +K_POINTS automatic + %(kx)d %(ky)d %(kz)d %(shift)d %(shift)d %(shift)d +''' + +# PH input file +ph_in=''' +%(calc)s +&INPUTPH + prefix = '%(prefix)s', + outdir = './%(outdir)s/', + ldisp = .true., + fildrho = 'fildrho', + fildvscf = 'fildvscf', + nq1 = %(nq)d , nq2 = %(nq)d , nq3 = %(nq)d + / +''' + +q2r_in=''' +&INPUT + fildyn='matdyn', + zasr='%(asr)s', + flfrc='%(prefix)s.fc' +/ +''' + +matdyn_in=''' +&input + asr='%(asr)s', + flfrc='%(prefix)s.fc', + flfrq='%(prefix)s.freq', + q_in_band_form=.true. +/ +''' + +phdos_in=''' +&input + dos=.true. + asr='%(asr)s', + flfrc='%(prefix)s.fc', + fldos='%(prefix)s.dos', + nk1=15, + nk2=15, + nk3=15, + ndos=%(ndos)d + / +''' + +def make_calc_dir(bdir,params): + ''' + Builds a calculation directory in the given base directory. + Creates a unique calculation directory and builds all ".in" + files for basic implemented functionality. + Prepares ".in" files for the following calculations: + + - pw.x scf calculation + - ph.x phonon calculation + - q2r.x real-space force constants calculation + - matdyn.x phonon dispersion calculation + - matdyn.x phonon density of states calculation + + INPUT + ----- + bdir - base directory + params - calculation parameters dictionary + + OUTPUT + ------ + cdir - name of the created calculation directory + + ''' + + cdir=params['prefix']+'.XXXX' + cdir=!cd $bdir ; mktemp -d $cdir + cdir=cdir[0] + bcdir='calc/'+cdir + #print cdir, bcdir + + open(bdir + cdir +'/pw.in','w').write(pw_in % params) + open(bdir + cdir +'/ph.in','w').write(ph_in % params) + open(bdir + cdir +'/q2r.in','w').write(q2r_in % params) + f=open(bdir + cdir+'/matdyn.in','w') + f.write(matdyn_in % params) + f.write('%d\n'%qp.shape[0]) + for v in qp: + f.write("%f %f %f %d\n" % (v[0],v[1],v[2],params['points'])) + f.close() + open(bdir+cdir+'/phdos.in','w').write(phdos_in % params) + return bcdir,cdir + +def make_D3_files(bdir,host,clc,cdat): + cdir = bdir + host + clc + open(cdir +'/phD3G.in','w').write(ph3G_in % cdat) + open(cdir +'/D3G.in','w').write(d3G_in % cdat) + open(cdir +('/phD3_%(sufix)s.in' % cdat),'w').write(ph3any_in % cdat) + open(cdir +('/D3_%(sufix)s.in' % cdat),'w').write(d3any_in % cdat) + + + + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8c28267 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[metadata] +description-file = README.md + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..da77de0 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from distutils.core import setup + +setup( + name='qeutil', + version='0.1', + packages=['qeutil',], + license='GPLv3', + long_description=open('README.md').read(), + description = 'A set of utilities for use of Quantum Espresso', + author = 'Paweł T. Jochym', + author_email = 'Pawel.Jochym@ifj.edu.pl', + url = 'https://github.com/jochym/qe-util', # use the URL to the github repo + download_url = 'https://github.com/jochym/qe-util/tarball/0.1', # I'll explain this in a second + keywords = ['science', 'physics', 'quantum-espresso'], # arbitrary keywords + classifiers = [], +)