def cif2structure(filename, primitive=False, symprec=0.001): """ Read a given file as a cif file, convert it into a ase atoms object and finally into a pychemia structure """ aseatoms = ase.io.read(filename) if primitive: #lattice, scaled_positions, numbers = spglib.refine_cell(aseatoms, symprec) #ref_atoms=ase.atoms(cell=lattice,scaled_positions=scaled_positions,numbers=numbers) lattice, scaled_positions, numbers = spglib.find_primitive(aseatoms, symprec) if lattice is not None and scaled_positions is not None and numbers is not None: fin_atoms = ase.atoms.Atoms(cell=lattice, scaled_positions=scaled_positions, numbers=numbers, pbc=True) else: fin_atoms = aseatoms else: fin_atoms = aseatoms ret = ase2pychemia(fin_atoms) return ret
def spglib_get_primitive(struct, **kwds): """Find primitive structure for given :class:`~pwtools.crys.Structure`. If `struct` is irreducible (is already a primitive cell), we return None, else a Structure. Uses pyspglib. Parameters ---------- struct : Structure **kwds : keywords passed to ``spglib.find_primitive()``, e.g. `symprec` and `angle_tolerance` last time I checked Returns ------- Structure or None Notes ----- spglib used to return (None,None,None) if no primitive cell can be found, i.e. the given input Structure cannot be reduced, which can occur if (a) a given Structure is already a primitive cell or (b) any other reason like a too small value of `symprec`. Now [py]spglib >= 1.8.x seems to always return data instead. We use :func:`is_same_struct` to determine if the struct is irreducible. In that case we return None in order to keep the API unchanged. Also note that a primitive cell (e.g. with 2 atoms) can have a number of different realizations. Therefore, you may not always get the primitive cell which you would expect or get from other tools like Wien2K's sgroup. Only things like `natoms` and the spacegroup can be safely compared. """ candidate = spglib2struct(spglib.find_primitive(struct.get_fake_ase_atoms(), **kwds)) if is_same_struct(candidate, struct): return None else: return candidate
def analyze_QHA_run(Calc, Tmin=1, Tmax=1500, Tsteps=100): ''' Run the QHA analysis procedure on the set of volume calculation Calc. This procedure produces an array with thermodynamic functions for all calculated volumes and given temperature range (the zero temperature is always included). Input ===== Calc - List of calculation data Tmin - Minimum temperature of the scan (K) Tmax - Maximum temperature of the scan (K) Tsteps - Number of temperature steps bdir - base directory (subdirectories host and calculation are located below) Output ====== qha - array of N x Tsteps x #params containing resulting data: V, T, Etot, Fph, P*V, Cv, S, ... for each calculation and each temperature step. ''' phdos={} qha=[] for n,c in enumerate(Calc): PCMUL=len(c.get_atomic_numbers())/len(spglib.find_primitive(c)[2]) # Read the calculated dos clc=c.calc.results phdos[n]=clc['phdos'] #Etot=c.get_potential_energy() Etot=clc['etotal']*Rydberg # Pressure returned in kbar we need to change it to eV/A^3 to get p*V in eV # eV/A^3=160.2176487GPa # kbar=0.1 GPa # eV/A^3 = 1602.176487 kbar P=clc['pressure']/1602.176487 #P=c.get_isotropic_pressure(c.get_stress()) # Scan the temperatures at this volume # Unit cell (primitive) volume in Bohr^3 => (A^3) #V=(calcQHA[idx][2]['A'])**3 #V=clc['volume']*(Bohr**3) V=c.get_volume()/PCMUL # get the ndf from the normalization of dos dos=phdos[n] ndf=round(simps(dos[1],dos[0])) # frequencies/energies # QE outputs dos in cm^-1, let's convert to sane units # Let's convert to energy to include the hbar nu=1e-3*cminv2meV*phdos[n][0] dos=phdos[n][1] # We need to cut the nu to positive range no=array([[o,g] for o,g in zip(nu,dos) if o>0 ]) nu=no[:,0] dos=no[:,1] # correct the normalization for units, negative cut and numerical errors dos=dos/simps(dos,nu) # plot in meV #plot(1e-3*nu,dos,label=n); #legend() #show() # plot the integrand at 300K #T=300 #plot(nu,dos*log(2*sinh(0.5*nu/(k_B*T))),label=idx) # Zero-point energy - $\hbar\omega/2$ for each degree of freedom integrated over $\omega$ F0=ndf*simps(0.5*dos*nu,nu) # Put in special case for the T=0. Just ZPV energy. qhav=[[V,0,Etot,F0,0,0,0,0]] qha.append(qhav) print '# %s: V=%.2f A^3, Etot=%.3f eV, P=%6.1f GPa' % (n, V, Etot, P/GPa) for T in linspace(Tmin,Tmax,Tsteps): # Maradudin book formula - the only one important for thermal expansion # The result is in eV, temperature in K e=0.5*nu/(k_B*T) Fph=ndf*T*k_B*simps(dos*log(2*sinh(e)),nu) Cv=ndf*k_B*simps(e*e*dos/sinh(e)**2,nu) S=k_B*(simps(2*dos*e/(exp(2*e)-1),nu)-simps(dos*(1-exp(-2*e)),nu)) # Alternative formula from QE Sqe=k_B*simps(dos*(e/tanh(e)-log(2*sinh(e))),nu) # Formulas lifted from the QHA code in QE - not correct with current units #q=0.5*a3*nu/T #E_int=simps(a1*dos*nu/tanh(q),nu) #S0=simps(dos*(q/tanh(q)-log(2*sinh(q))),nu) qhav.append([V, T, Etot, Fph, P*V, Cv, S, Sqe]) #print " %5.2f %12f %12f %12f" % (T, Cv, S, Sqe) return array(qha)
def get_periodic_symmetry_operations(atoms, error_tolerance=0.01, debug=False): from ase.utils import irotate from ase.visualize import view from pyspglib import spglib #symmetry = spglib.get_symmetry(atoms, symprec=1e-5) symmetry = spglib.get_symmetry(atoms, symprec=1e-2) dataset = spglib.get_symmetry_dataset(atoms, symprec=1e-2) result = [] if debug: cell, scaled_positions, numbers = spglib.find_primitive(atoms, symprec=1e-5) a = Atoms(symbols='O4', cell=cell, scaled_positions=scaled_positions, pbc=True) #symmetry = spglib.get_symmetry(a, symprec=1e-2) #dataset = spglib.get_symmetry_dataset(a, symprec=1e-2) print dataset['equivalent_atoms'] print dataset['international'] print dataset['hall'] print dataset['wyckoffs'] print dataset['transformation_matrix'] print "Number of symmetry operations %i" % len(dataset['rotations']) for i in range(dataset['rotations'].shape[0]): new_atoms = atoms.copy() test = atoms.copy() rot = dataset['rotations'][i] trans = dataset['translations'][i] if debug: x, y, z = irotate(rot) #print x, y, z print "------------------- %i -----------------------" % i print "Rotation" print rot print "Translation" print trans new_pos = new_atoms.get_scaled_positions() for l, pos in enumerate(new_atoms.get_scaled_positions()): #print new_pos[l] new_pos[l] = (numpy.dot(rot, pos)) new_pos[l] += trans #print new_pos[l] new_atoms.set_scaled_positions(new_pos) equals = get_equals_periodic(atoms, new_atoms, error_tolerance=error_tolerance, debug=debug) if equals != None: so = SymmetryOperation(str(i), equals, None, vector=None, magnitude=1, rotation_matrix=rot, translation_vector=trans) #if debug: # print so result.append(so) else: print "Equivalent not found" #view(test) #view(new_atoms) #raw_input() return result
def write_cell_params(fh, a, p): ''' Write the specification of the cell using a traditional A,B,C, alpha, beta, gamma scheme. The symmetry and parameters are determined from the atoms (a) object. The atoms object is translated into a primitive unit cell but *not* converted. This is just an internal procedure. If you wish to work with primitive unit cells in ASE, you need to make a conversion yourself. The cell params are in angstrom. Input ----- fh file handle of the opened pw.in file a atoms object p parameters dictionary Output ------ Primitive cell tuple of arrays: (lattice, atoms, atomic numbers) ''' assert(p['use_symmetry']) # Get a spacegroup name and number for the a sg,sgn=spglib.get_spacegroup(a).split() # Extract the number sgn=int(sgn[1:-1]) # Extract the lattice type ltyp=sg[0] # Find a primitive unit cell for the system # puc is a tuple of (lattice, atoms, atomic numbers) puc=spglib.find_primitive(a) cell=puc[0] apos=puc[1] anum=puc[2] icell=a.get_cell() A=norm(icell[0]) B=norm(icell[1]) C=norm(icell[2]) # Select appropriate ibrav if sgn >= 195 : # Cubic lattice if ltyp=='P': p['ibrav']=1 # Primitive qepc=array([[1,0,0],[0,1,0],[0,0,1]]) elif ltyp=='F': p['ibrav']=2 # FCC qepc=array([[-1,0,1],[0,1,1],[-1,1,0]])/2.0 elif ltyp=='I': p['ibrav']=3 # BCC qepc=array([[1,1,1],[-1,1,1],[-1,-1,1]])/2.0 else : print 'Impossible lattice symmetry! Contact the author!' raise NotImplementedError #a=sqrt(2)*norm(cell[0]) qepc=A*qepc fh.write(' A = %f,\n' % (A,)) elif sgn >= 143 : # Hexagonal and trigonal if ltyp=='P' : p['ibrav']=4 # Primitive qepc=array([[1,0,0],[-1/2,sqrt(3)/2,0],[0,0,C/A]]) elif ltyp=='R' : p['ibrav']=5 # Trigonal rhombohedral raise NotImplementedError else : print 'Impossible lattice symmetry! Contact the author!' raise NotImplementedError qepc=A*qepc fh.write(' A = %f,\n' % (A,)) fh.write(' C = %f,\n' % (C,)) elif sgn >= 75 : raise NotImplementedError elif sgn ==1 : # P1 symmetry - no special primitive cell signal to the caller p['ibrav']=0 return None else : raise NotImplementedError cp=Atoms(cell=puc[0], scaled_positions=puc[1], numbers=puc[2], pbc=True) qepc=Atoms(cell=qepc, positions=cp.get_positions(), numbers=cp.get_atomic_numbers(), pbc=True) return qepc.get_cell(), qepc.get_scaled_positions(), qepc.get_atomic_numbers()
def analyze_QHA_run(Calc, Tmin=1, Tmax=1500, Tsteps=100): ''' Run the QHA analysis procedure on the set of volume calculation Calc. This procedure produces an array with thermodynamic functions for all calculated volumes and given temperature range (the zero temperature is always included). Input ===== Calc - List of calculation data Tmin - Minimum temperature of the scan (K) Tmax - Maximum temperature of the scan (K) Tsteps - Number of temperature steps bdir - base directory (subdirectories host and calculation are located below) Output ====== qha - array of N x Tsteps x #params containing resulting data: V, T, Etot, Fph, P*V, Cv, S, ... for each calculation and each temperature step. ''' phdos = {} qha = [] for n, c in enumerate(Calc): PCMUL = len(c.get_atomic_numbers()) / len(spglib.find_primitive(c)[2]) # Read the calculated dos clc = c.calc.results phdos[n] = clc['phdos'] #Etot=c.get_potential_energy() Etot = clc['etotal'] * Rydberg # Pressure returned in kbar we need to change it to eV/A^3 to get p*V in eV # eV/A^3=160.2176487GPa # kbar=0.1 GPa # eV/A^3 = 1602.176487 kbar P = clc['pressure'] / 1602.176487 #P=c.get_isotropic_pressure(c.get_stress()) # Scan the temperatures at this volume # Unit cell (primitive) volume in Bohr^3 => (A^3) #V=(calcQHA[idx][2]['A'])**3 #V=clc['volume']*(Bohr**3) V = c.get_volume() / PCMUL # get the ndf from the normalization of dos dos = phdos[n] ndf = round(simps(dos[1], dos[0])) # frequencies/energies # QE outputs dos in cm^-1, let's convert to sane units # Let's convert to energy to include the hbar nu = 1e-3 * cminv2meV * phdos[n][0] dos = phdos[n][1] # We need to cut the nu to positive range no = array([[o, g] for o, g in zip(nu, dos) if o > 0]) nu = no[:, 0] dos = no[:, 1] # correct the normalization for units, negative cut and numerical errors dos = dos / simps(dos, nu) # plot in meV #plot(1e-3*nu,dos,label=n); #legend() #show() # plot the integrand at 300K #T=300 #plot(nu,dos*log(2*sinh(0.5*nu/(k_B*T))),label=idx) # Zero-point energy - $\hbar\omega/2$ for each degree of freedom integrated over $\omega$ F0 = ndf * simps(0.5 * dos * nu, nu) # Put in special case for the T=0. Just ZPV energy. qhav = [[V, 0, Etot, F0, 0, 0, 0, 0]] qha.append(qhav) print '# %s: V=%.2f A^3, Etot=%.3f eV, P=%6.1f GPa' % (n, V, Etot, P / GPa) for T in linspace(Tmin, Tmax, Tsteps): # Maradudin book formula - the only one important for thermal expansion # The result is in eV, temperature in K e = 0.5 * nu / (k_B * T) Fph = ndf * T * k_B * simps(dos * log(2 * sinh(e)), nu) Cv = ndf * k_B * simps(e * e * dos / sinh(e)**2, nu) S = k_B * (simps(2 * dos * e / (exp(2 * e) - 1), nu) - simps(dos * (1 - exp(-2 * e)), nu)) # Alternative formula from QE Sqe = k_B * simps(dos * (e / tanh(e) - log(2 * sinh(e))), nu) # Formulas lifted from the QHA code in QE - not correct with current units #q=0.5*a3*nu/T #E_int=simps(a1*dos*nu/tanh(q),nu) #S0=simps(dos*(q/tanh(q)-log(2*sinh(q))),nu) qhav.append([V, T, Etot, Fph, P * V, Cv, S, Sqe]) #print " %5.2f %12f %12f %12f" % (T, Cv, S, Sqe) return array(qha)