def V(C_rad=None, calculated=[], get_calculated=False): if get_calculated: return calculated elif C_rad is not None: zmolecule = get_zm_from_C(C_rad) result = calculate(molecule=zmolecule, forces=True, el_calc_input=el_calc_input, **kwargs) energy = convertor(result.scfenergies[0], 'eV', 'hartree') grad_energy_X = result.grads[0] / convertor(1, 'bohr', 'Angstrom') grad_X = zmolecule.get_grad_cartesian( as_function=False, drop_auto_dummies=True) grad_energy_C = np.sum( grad_energy_X.T[:, :, None, None] * grad_X, axis=(0, 1)) for i in range(min(3, grad_energy_C.shape[0])): grad_energy_C[i, i:] = 0 zmolecule.metadata['energy'] = energy zmolecule.metadata['grad_energy'] = grad_energy_C calculated.append({'energy': energy, 'grad_energy': grad_energy_C, 'zmolecule': zmolecule}) with open(md_out, 'a') as f: f.write(_get_table_row(calculated, grad_energy_X)) return energy, grad_energy_C.flatten() else: raise ValueError
def _calculate_dipole(self, charges, coords, origin): """Calculate the dipole moment from the given atomic charges and their coordinates with respect to the origin. """ transl_coords_au = convertor(coords - origin, 'Angstrom', 'bohr') dipole = numpy.dot(charges, transl_coords_au) return convertor(dipole, 'ebohr', 'Debye')
def test_converged_structures(): """Tests that non converged coordinates in Gaussian log files are discarded""" structure = get_fun('MPR.psf') scan = qmdb.parse_gauss(get_fun('MPR.scan1.pos.log'), structure) log = Gaussian(get_fun('MPR.scan1.pos.log')) data = log.parse() assert_array_equal(data.atomcoords.shape, scan.xyz.shape) converted = convertor(data.scfenergies, "eV", "kJmol-1") - \ min(convertor(data.scfenergies[:len(data.atomcoords)], "eV", "kJmol-1")) assert_array_equal(converted[:47], scan.qm_energy)
def test_converged_structures(): """Tests that non converged coordinates in Gaussian log files are discarded""" structure = get_fn('MPR.psf') scan = qmdb.parse_gauss(get_fn('MPR.scan1.pos.log'), structure) log = Gaussian(get_fn('MPR.scan1.pos.log')) data = log.parse() assert_array_equal(data.atomcoords.shape, scan.xyz.shape) converted = convertor(data.scfenergies, "eV", "kJmol-1") - \ min(convertor(data.scfenergies[:len(data.atomcoords)], "eV", "kJmol-1")) assert_array_equal(converted[:47], scan.qm_energy)
def makepyscf_mos(ccdata, mol): """ Returns pyscf formatted MO properties from a cclib object. Parameters --- ccdata: cclib object cclib object from parsed output mol: pyscf Molecule object molecule object that must contain the mol.basis attribute Returns ---- mo_coeff : n_spin x nmo x nao ndarray molecular coeffcients, unnormalized according to pyscf standards mo_occ : array molecular orbital occupation mo_syms : array molecular orbital symmetry labels mo_energies: array molecular orbital energies in units of Hartree """ inputattrs = ccdata.__dict__ if "mocoeffs" in inputattrs: mol.build() s = mol.intor('int1e_ovlp') if np.shape(ccdata.mocoeffs)[0] == 1: mo_coeffs = np.einsum('i,ij->ij', np.sqrt(1 / s.diagonal()), ccdata.mocoeffs[0].T) mo_occ = np.zeros(ccdata.nmo) mo_occ[:ccdata.homos[0] + 1] = 2 mo_energies = convertor(np.array(ccdata.moenergies), "eV", "hartree") if hasattr(ccdata, 'mosyms'): mo_syms = ccdata.mosyms else: mo_syms = np.full_like(ccdata.moenergies, 'A', dtype=str) elif np.shape(ccdata.mocoeffs)[0] == 2: mo_coeff_a = np.einsum('i,ij->ij', np.sqrt(1 / s.diagonal()), ccdata.mocoeffs[0].T) mo_coeff_b = np.einsum('i,ij->ij', np.sqrt(1 / s.diagonal()), ccdata.mocoeffs[1].T) mo_occ = np.zeros((2, ccdata.nmo)) mo_occ[0, :ccdata.homos[0] + 1] = 1 mo_occ[1, :ccdata.homos[1] + 1] = 1 mo_coeffs = np.array([mo_coeff_a, mo_coeff_b]) mo_energies = convertor(np.array(ccdata.moenergies), "eV", "hartree") if hasattr(ccdata, 'mosyms'): mo_syms = ccdata.mosyms else: mo_syms = np.full_like(ccdata.moenergies, 'A', dtype=str) return mo_coeffs, mo_occ, mo_syms, mo_energies
def _mo_energies(self): """Section: Molecular Orbital Energies.""" mo_energies = [] alpha_elctrons = self._no_alpha_electrons() beta_electrons = self._no_beta_electrons() for mo_energy in self.ccdata.moenergies[0][:alpha_elctrons]: mo_energies.append(WFX_FIELD_FMT % ( utils.convertor(mo_energy, 'eV', 'hartree'))) if self.ccdata.mult > 1: for mo_energy in self.ccdata.moenergies[1][:beta_electrons]: mo_energies.append(WFX_FIELD_FMT % ( utils.convertor(mo_energy, 'eV', 'hartree'))) return mo_energies
def wavefunction(coords, mocoeffs, gbasis, volume): """Calculate the magnitude of the wavefunction at every point in a volume. Attributes: coords -- the coordinates of the atoms mocoeffs -- mocoeffs for one eigenvalue gbasis -- gbasis from a parser object volume -- a template Volume object (will not be altered) """ bfs = getbfs(coords, gbasis) wavefn = copy.copy(volume) wavefn.data = numpy.zeros( wavefn.data.shape, "d") conversion = convertor(1,"bohr","Angstrom") x = numpy.arange(wavefn.origin[0], wavefn.topcorner[0]+wavefn.spacing[0], wavefn.spacing[0]) / conversion y = numpy.arange(wavefn.origin[1], wavefn.topcorner[1]+wavefn.spacing[1], wavefn.spacing[1]) / conversion z = numpy.arange(wavefn.origin[2], wavefn.topcorner[2]+wavefn.spacing[2], wavefn.spacing[2]) / conversion for bs in range(len(bfs)): data = numpy.zeros( wavefn.data.shape, "d") for i,xval in enumerate(x): for j,yval in enumerate(y): for k,zval in enumerate(z): data[i, j, k] = bfs[bs].amp(xval,yval,zval) numpy.multiply(data, mocoeffs[bs], data) numpy.add(wavefn.data, data, wavefn.data) return wavefn
def test_ECCD(): filename = os.path.join("exampleCircularDichroism", "OPT_td.out") clearoutput(filename) data = parse(filename) Sigma = 2.402185 # Because it corresponds to a FWHM of 2.0 ET(None, sys.stdout, data, filename, 100, 300, 10000, Sigma, False, False) spectrum = [ list(map(float, x.split()[:3])) for x in open(os.path.join(gaussdir(filename), "CDSpectrum.txt")) if not x[0] == "E" ] # Assert Peak Max maxval = max([x[2] for x in spectrum]) assert abs(maxval - 0.143) < 0.001 # Assert Sigma...(full width at 1/e height) one_over_e_max = maxval / math.e for x, y, z in spectrum: if abs(z - one_over_e_max) < 0.00002: one_over_e_x = x if z == maxval: max_x = x fullwidth = (one_over_e_x - max_x) * 2. width_in_eV = fullwidth / convertor(1., "eV", "cm-1") assert abs(width_in_eV - Sigma) < 0.01
def getbfs(ccdata): bfs = [] # `atom` is instance of pyquante2.geo.atom.atom class. for i, atom in enumerate(ccdata.atomcoords[-1]): # `basis` is basis coefficients stored in ccData. basis = ccdata.gbasis[i] for sym, primitives in basis: # `sym` is S, P, D, F and is used as key here. for power in sym2powerlist[sym]: exponentlist = [] coefficientlist = [] for exponents, coefficients in primitives: exponentlist.append(exponents) coefficientlist.append(coefficients) basisfunction = cgbf( convertor(atom, "Angstrom", "bohr"), powers=power, exps=exponentlist, coefs=coefficientlist, ) basisfunction.normalize() bfs.append(basisfunction) return bfs
def writeascube(self, filename): # Remember that the units are bohr, not Angstroms convert = lambda x : convertor(x, "Angstrom", "bohr") ans = [] ans.append("Cube file generated by cclib") ans.append("") format = "%4d%12.6f%12.6f%12.6f" origin = [convert(x) for x in self.origin] ans.append(format % (0, origin[0], origin[1], origin[2])) ans.append(format % (self.data.shape[0], convert(self.spacing[0]), 0.0, 0.0)) ans.append(format % (self.data.shape[1], 0.0, convert(self.spacing[1]), 0.0)) ans.append(format % (self.data.shape[2], 0.0, 0.0, convert(self.spacing[2]))) line = [] for i in range(self.data.shape[0]): for j in range(self.data.shape[1]): for k in range(self.data.shape[2]): line.append(scinotation(self.data[i][j][k])) if len(line)==6: ans.append(" ".join(line)) line = [] if line: ans.append(" ".join(line)) line = [] outputfile = open(filename, "w") outputfile.write("\n".join(ans)) outputfile.close()
def __init__(self, output_path, cluster_label, partition_label, md_temp, grad_e_unit, grad_r_unit, md_iter=0, e_unit='kcal/mol', r_unit='Angstrom', theory='unknown'): self.output_path = output_path self.output_name = self.output_path.split('/')[-1].split('.')[0] self.cluster_label = cluster_label self.partition_label = partition_label self.partition_size = int(len(self.partition_label)) self.md_temp = md_temp self.md_iter = md_iter self.e_unit = e_unit self.r_unit = r_unit self.theory = theory self.cclib_data = ccread(self.output_path) self._get_gdml_data() self.E = convertor(self.E, 'eV', self.e_unit) self.G = convert_forces(self.G, grad_e_unit, grad_r_unit, self.e_unit, self.r_unit) self.F = np.negative(self.G)
def calculate_H(self): """ Calculate H_A(r_A) This is a quantity introduced in DDEC6 as a constraint preventing the tails from being too contracted. [STEP 4-7] """ self._h = [] for atomi in range(len(self._g)): # First set H_est as G_A self._h.append(self._g[atomi]) # Determine eta_upper using equation 86 in doi: 10.1039/c6ra04656h # and apply upper limit using equation 91. temp = ( 1 - (self.tau[atomi])**2 + self.convergence_level ) # convergence_level is added to avoid divide-by-zero in next line for highly polar molecules. eta = 2.5 * convertor(1, "Angstrom", "bohr") / temp exp_applied = self._h[atomi][:-1] * numpy.exp( -1 * eta[1:] * numpy.diff(self.radial_grid_r[atomi])) for radiusi in range(1, len(self._h[atomi])): self._h[atomi][radiusi] = max(self._h[atomi][radiusi], exp_applied[radiusi - 1]) # Normalize using equation 92 in doi: 10.1039/c6ra04656h. self._h[atomi] = ( self._h[atomi] * self._integrate_from_radial([self._g[atomi]], [atomi]) / self._integrate_from_radial([self._h[atomi]], [atomi]))
def get_optimisation_E(self, units='eV'): energies = self._opt_data.read('scfenergies') if not units == 'eV': energies = convertor(energies, 'eV', units) return energies[-1]
def test_makepyscf_mos(self): pyscfmol = cclib2pyscf.makepyscf(self.data) mo_coeff, mo_occ, mo_syms, mo_energies = cclib2pyscf.makepyscf_mos(self.data,pyscfmol) assert np.allclose(mo_energies,convertor(np.array(self.data.moenergies),"eV","hartree")) # check first MO coefficient assert np.allclose(mo_coeff[0][0], self.data.mocoeffs[0][0][0]) # check a random middle MO coefficient assert np.allclose(mo_coeff[0][10],self.data.mocoeffs[0][10][0]) # test unrestricted code. pyscfmol = cclib2pyscf.makepyscf(self.udata) mo_coeff, mo_occ, mo_syms, mo_energies = cclib2pyscf.makepyscf_mos(self.udata,pyscfmol) assert np.allclose(mo_energies,convertor(np.array(self.udata.moenergies),"eV","hartree")) # check first MO coefficient assert np.allclose(mo_coeff[0][0][0], self.udata.mocoeffs[0][0][0]) # check a random middle MO coefficient assert np.allclose(mo_coeff[0][0][10],self.udata.mocoeffs[0][10][0])
def integrate_square(self): boxvol = ( self.spacing[0] * self.spacing[1] * self.spacing[2] * convertor(1, "Angstrom", "bohr") ** 3 ) return sum(self.data.ravel() ** 2) * boxvol
def _nuclear_coords(self): """Section: Nuclear Cartesian Coordinates. Nuclear coordinates in Bohr.""" coord_template = WFX_FIELD_FMT * 3 to_bohr = lambda x: utils.convertor(x, 'Angstrom', 'bohr') nuc_coords = [coord_template % tuple(to_bohr(coord)) for coord in self.ccdata.atomcoords[-1]] return nuc_coords
def integrate(self, weights=None): weights = numpy.ones_like(self.data) if weights is None else weights assert (weights.shape == self.data.shape ), "Shape of weights do not match with shape of Volume data." boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * convertor(1, "Angstrom", "bohr")**3) return numpy.sum(self.data * weights) * boxvol
def _calculate_quadrupole(self, charges, coords, origin): """Calculate the traceless quadrupole moment from the given atomic charges and their coordinates with respect to the origin. """ transl_coords_au = convertor(coords - origin, 'Angstrom', 'bohr') delta = numpy.eye(3) Q = numpy.zeros([3, 3]) for i in range(3): for j in range(3): for q, r in zip(charges, transl_coords_au): Q[i,j] += 1/2 * q * (3 * r[i] * r[j] - \ numpy.linalg.norm(r)**2 * delta[i,j]) triu_idxs = numpy.triu_indices_from(Q) raveled_idxs = numpy.ravel_multi_index(triu_idxs, Q.shape) quadrupole = numpy.take(Q.flatten(), raveled_idxs) return convertor(quadrupole, 'ebohr2', 'Buckingham')
def integrate_square(self, weights=None): weights = numpy.ones_like(self.data) if weights is None else weights boxvol = ( self.spacing[0] * self.spacing[1] * self.spacing[2] * convertor(1, "Angstrom", "bohr") ** 3 ) return numpy.sum((self.data * weights) ** 2) * boxvol
def main(): """The main routine!""" parser = argparse.ArgumentParser() parser.add_argument('compchemfilename', nargs='+') parser.add_argument('--scaling-energy-change', type=float, default=10.0) args = parser.parse_args() compchemfilenames = args.compchemfilename for compchemfilename in compchemfilenames: stub = os.path.splitext(compchemfilename)[0] job = ccopen(compchemfilename) data = job.parse() fig, ax = plt.subplots() if type(job) == cclib.parser.qchemparser.QChem: scfenergies = [ utils.convertor(scfenergy, 'eV', 'hartree') for scfenergy in data.scfenergies ] gradients = [geovalue[0] for geovalue in data.geovalues] displacements = [geovalue[1] for geovalue in data.geovalues] energy_changes = [(geovalue[2] * args.scaling_energy_change) for geovalue in data.geovalues] # If this isn't true, something funny happened during the # parsing, so fail out. assert len(scfenergies) == len(gradients) steps = range(1, len(scfenergies) + 1) # ax.plot(steps, scfenergies, label='SCF energy') ax.plot(steps, gradients, label='max gradient') ax.plot(steps, displacements, label='max displacement') ax.plot(steps, energy_changes, label='energy change') ax.set_title(stub) ax.set_xlabel('optimization step #') elif type(job) == cclib.parser.orcaparser.ORCA: pass else: pass ax.legend(loc='best', fancybox=True) fig.savefig(stub + '.pdf', bbox_inches='tight')
def convertR(self, R_units): """Convert coordinates and updates ``r_unit``. Parameters ---------- R_units : :obj:`str` Desired units of coordinates. Options are ``'Angstrom'`` or ``'bohr'``. """ self._R = convertor(self.R, self.r_unit, R_units) self.r_unit = R_units
def qchem_get_cisd_energies(inputfile, energy_gs): state_energies = [] line = '' while 'CIS(D) excitation energy' not in line: line = next(inputfile) while list(set(line.strip())) != ['-']: if 'CIS(D) excitation energy' in line: # Stupid Q-Chem! energy_es = energy_gs + convertor(float(line.split()[-2]), 'eV', 'hartree') state_energies.append(energy_es) line = next(inputfile) return state_energies
def _parse_mosyms_moenergies(self, inputfile, spinidx): """Parse molecular orbital symmetries and energies from the 'Post-Iterations' section. """ line = next(inputfile) while line.strip(): for i in range(len(line.split()) // 2): self.mosyms[spinidx].append(line.split()[i*2][-2:]) moenergy = utils.convertor(float(line.split()[i*2+1]), "hartree", "eV") self.moenergies[spinidx].append(moenergy) line = next(inputfile) return
def test_nre(self): """Testing nuclear repulsion energy for one logfile where it is printed.""" data, logfile = getdatafile(QChem, "basicQChem4.2", "water_mp4sdq.out") nuclear = Nuclear(data) nuclear.logger.setLevel(logging.ERROR) with open(logfile.filename) as f: output = f.read() line = re.search('Nuclear Repulsion Energy = .* hartrees', output).group() nre = float(line.split()[4]) nre = utils.convertor(nre, 'Angstrom', 'bohr') self.assertAlmostEqual(nuclear.repulsion_energy(), nre, places=7)
def test_repulsion_energy(self): """Testing nuclear repulsion energy for one logfile where it is printed.""" data, logfile = getdatafile(QChem, "basicQChem5.4", ["water_mp4sdq.out"]) nuclear = Nuclear(data) nuclear.logger.setLevel(logging.ERROR) with open(logfile.filename) as f: output = f.read() line = re.search('Nuclear Repulsion Energy = .* hartrees', output).group() nre = float(line.split()[4]) nre = utils.convertor(nre, 'Angstrom', 'bohr') self.assertAlmostEqual(nuclear.repulsion_energy(), nre, places=7)
def reshape_G(self): """ Calculate G_A(r_A) and reshape densities This is a quantity introduced in DDEC6 as a constraint preventing the tails from being too diffuse. [STEP 4-7] """ self._candidates_bigPhi = [] self._candidates_phi = [] # Initial conditions are detailed in Figure S3 in doi: 10.1039/c6ra04656h phiAII = numpy.zeros_like(self.data.atomnos, dtype=float) bigphiAII = numpy.zeros_like(self.data.atomnos, dtype=float) self._g = [] self._eta = [] for atomi in range(self.data.natom): # G_A -- equation S102 in doi: 10.1039/c6ra04656h self._g.append( numpy.zeros_like(self.proatom_density[atomi], dtype=float)) self._g[atomi] = self.rho_wavg[ atomi] + bigphiAII[atomi] * numpy.sqrt(self.rho_wavg[atomi]) # Exponential constraint (as expressed in equation S105) self._eta.append((1 - (self.tau[atomi])**2) * 1.75 * convertor(1, "Angstrom", "bohr")) exp_applied = self._g[atomi][:-1] * numpy.exp( -1 * self._eta[atomi][1:] * numpy.diff(self.radial_grid_r[atomi])) for radiusi in range(1, len(self._g[atomi])): self._g[atomi][radiusi] = min(self._g[atomi][radiusi], exp_applied[radiusi - 1]) # phi_A^II -- Equation S106 in doi: 10.1039/c6ra04656h phiAII[atomi] = self._integrate_from_radial( [self._g[atomi] - self.rho_wavg[atomi]], [atomi]) self._candidates_bigPhi.append([bigphiAII[atomi]]) self._candidates_phi.append([phiAII[atomi]]) # Attempt to find the point where phiAI is zero iteratively # Refer to S101 in doi: 10.1039/c6ra04656h self._candidates_phi[atomi], self._candidates_bigPhi[ atomi] = self._converge_phi(phiAII[atomi], 1, atomi) # Perform parabolic fit to find optimized phiAI # Refer to Figure S1 in doi: 10.1039/c6ra04656h bigphiAII[atomi] = self._parabolic_fit(self.rho_wavg[atomi], 2, atomi) # Set final G_A value using chosen Phi self._g[atomi] = self._update_phiaii(self.rho_wavg[atomi], bigphiAII[atomi], atomi)[1]
def main(): """The main routine!""" parser = argparse.ArgumentParser() parser.add_argument('compchemfilename', nargs='+') parser.add_argument('--scaling-energy-change', type=float, default=10.0) args = parser.parse_args() compchemfilenames = args.compchemfilename for compchemfilename in compchemfilenames: stub = os.path.splitext(compchemfilename)[0] job = ccopen(compchemfilename) data = job.parse() fig, ax = plt.subplots() if type(job) == cclib.parser.qchemparser.QChem: scfenergies = [utils.convertor(scfenergy, 'eV', 'hartree') for scfenergy in data.scfenergies] gradients = [geovalue[0] for geovalue in data.geovalues] displacements = [geovalue[1] for geovalue in data.geovalues] energy_changes = [(geovalue[2] * args.scaling_energy_change) for geovalue in data.geovalues] # If this isn't true, something funny happened during the # parsing, so fail out. assert len(scfenergies) == len(gradients) steps = range(1, len(scfenergies) + 1) # ax.plot(steps, scfenergies, label='SCF energy') ax.plot(steps, gradients, label='max gradient') ax.plot(steps, displacements, label='max displacement') ax.plot(steps, energy_changes, label='energy change') ax.set_title(stub) ax.set_xlabel('optimization step #') elif type(job) == cclib.parser.orcaparser.ORCA: pass else: pass ax.legend(loc='best', fancybox=True) fig.savefig(stub + '.pdf', bbox_inches='tight')
def getGrid(vol): """Helper function that returns (x, y, z), each of which are numpy array of the values that correspond to grid points. Input: vol -- Volume object (will not be altered) """ conversion = convertor(1, "bohr", "Angstrom") gridendpt = vol.topcorner + 0.5 * vol.spacing x = numpy.arange(vol.origin[0], gridendpt[0], vol.spacing[0]) / conversion y = numpy.arange(vol.origin[1], gridendpt[1], vol.spacing[1]) / conversion z = numpy.arange(vol.origin[2], gridendpt[2], vol.spacing[2]) / conversion return (x, y, z)
def plot_optimisation_E(self, units='eV'): energies = self._opt_data.read('scfenergies') for data in reversed(self._prev_opt_data): energies = np.concatenate([data.read('scfenergies'), energies]) if not units == 'eV': energies = convertor(energies, 'eV', units) f, ax = plt.subplots() ax.plot(energies) ax.set_ylabel('Energy ({0})'.format(units)) ax.set_xlabel('Optimisation Step') ax.grid(True) return ax
def read_energy_levels(input_file, units='eV'): """ Determines the energy levels and homos from and output file :param input_file: input file to read :param units: units to return energies in """ try: data = ccopen(input_file).parse() levels = np.array(data.moenergies) if units != 'eV': try: levels = convertor(levels, 'eV', units) except KeyError as e: raise KeyError(f'Cannot convert energy levels to {units}') except AttributeError as e: raise Exception('Cannot find appropriate data, has the SCF finished yet?') return levels, data.homos
def main(): """The main routine!""" parser = argparse.ArgumentParser() parser.add_argument('compchemfilename', nargs='+') args = parser.parse_args() compchemfilenames = args.compchemfilename for compchemfilename in compchemfilenames: stub = os.path.splitext(compchemfilename)[0] job = ccopen(compchemfilename) data = job.parse() fig, ax = plt.subplots() if type(job) == cclib.parser.qchemparser.QChem: scfenergies = [ utils.convertor(scfenergy, 'eV', 'hartree') for scfenergy in data.scfenergies ] print(scfenergies) # scfenergies = [scfenergy for scfenergy in data.scfenergies] steps = range(1, len(scfenergies) + 1) ax.plot(steps, scfenergies, label='SCF energy') ax.set_title(stub) ax.set_xlabel('SCF step #') ax.set_xticks(steps) elif type(job) == cclib.parser.orcaparser.ORCA: pass else: pass ax.legend(loc='best', fancybox=True) fig.savefig(stub + '.pdf', bbox_inches='tight')
def _mo_from_ccdata(self): """Create [MO] section. Sym= symmetry_label_1 Ene= mo_energy_1 Spin= (Alpha|Beta) Occup= mo_occupation_number_1 ao_number_1 mo_coefficient_1 ... ao_number_n mo_coefficient_n ... """ moenergies = self.ccdata.moenergies mocoeffs = self.ccdata.mocoeffs homos = self.ccdata.homos mult = self.ccdata.mult has_syms = False lines = [] # Sym attribute is optional in [MO] section. if hasattr(self.ccdata, 'mosyms'): has_syms = True syms = self.ccdata.mosyms spin = 'Alpha' for i in range(mult): for j in range(len(moenergies[i])): if has_syms: lines.append(' Sym= %s' % syms[i][j]) moenergy = utils.convertor(moenergies[i][j], 'eV', 'hartree') lines.append(' Ene= {:10.4f}'.format(moenergy)) lines.append(' Spin= %s' % spin) if j <= homos[i]: lines.append(' Occup= {:10.6f}'.format(2.0 / mult)) else: lines.append(' Occup= {:10.6f}'.format(0.0)) # Rearrange mocoeffs according to Molden's lexicographical order. mocoeffs[i][j] = self._rearrange_mocoeffs(mocoeffs[i][j]) for k, mocoeff in enumerate(mocoeffs[i][j]): lines.append('{:4d} {:10.6f}'.format(k + 1, mocoeff)) spin = 'Beta' return lines
def electrondensity(coords, mocoeffslist, gbasis, volume): """Calculate the magnitude of the electron density at every point in a volume. Attributes: coords -- the coordinates of the atoms mocoeffs -- mocoeffs for all of the occupied eigenvalues gbasis -- gbasis from a parser object volume -- a template Volume object (will not be altered) Note: mocoeffs is a list of NumPy arrays. The list will be of length 1 for restricted calculations, and length 2 for unrestricted. """ bfs = getbfs(coords, gbasis) density = copy.copy(volume) density.data = numpy.zeros(density.data.shape, "d") conversion = convertor(1, "bohr", "Angstrom") x = numpy.arange(density.origin[0], density.topcorner[0] + density.spacing[0], density.spacing[0]) / conversion y = numpy.arange(density.origin[1], density.topcorner[1] + density.spacing[1], density.spacing[1]) / conversion z = numpy.arange(density.origin[2], density.topcorner[2] + density.spacing[2], density.spacing[2]) / conversion for mocoeffs in mocoeffslist: for mocoeff in mocoeffs: wavefn = numpy.zeros(density.data.shape, "d") for bs in range(len(bfs)): data = numpy.zeros(density.data.shape, "d") for i, xval in enumerate(x): for j, yval in enumerate(y): tmp = [] for zval in z: tmp.append(bfs[bs].amp(xval, yval, zval)) data[i, j, :] = tmp data *= mocoeff[bs] wavefn += data density.data += wavefn**2 # TODO ROHF if len(mocoeffslist) == 1: density.data *= 2.0 return density
def _energy(self): """Section: Energy = T + Vne + Vee + Vnn. The total energy of the molecule. HF and KSDFT: SCF energy (scfenergies), MP2 : MP2 total energy (mpenergies), CCSD : CCSD total energy (ccenergies). """ energy = 0 if hasattr(self.ccdata, 'ccenergies'): energy = self.ccdata.ccenergies[-1] elif hasattr(self.ccdata, 'mpenergies'): energy = self.ccdata.mpenergies[-1][-1] elif hasattr(self.ccdata, 'scfenergies'): energy = self.ccdata.scfenergies[-1] else: raise filewriter.MissingAttributeError( 'scfenergies/mpenergies/ccenergies') return WFX_FIELD_FMT % (utils.convertor(energy, 'eV', 'hartree'))
def electrondensity(coords, mocoeffslist, gbasis, volume): """Calculate the magnitude of the electron density at every point in a volume. Attributes: coords -- the coordinates of the atoms mocoeffs -- mocoeffs for all of the occupied eigenvalues gbasis -- gbasis from a parser object volume -- a template Volume object (will not be altered) Note: mocoeffs is a list of NumPy arrays. The list will be of length 1 for restricted calculations, and length 2 for unrestricted. """ bfs = getbfs(coords, gbasis) density = copy.copy(volume) density.data = numpy.zeros(density.data.shape, "d") conversion = convertor(1, "bohr", "Angstrom") x = numpy.arange(density.origin[0], density.topcorner[0] + density.spacing[0], density.spacing[0]) / conversion y = numpy.arange(density.origin[1], density.topcorner[1] + density.spacing[1], density.spacing[1]) / conversion z = numpy.arange(density.origin[2], density.topcorner[2] + density.spacing[2], density.spacing[2]) / conversion for mocoeffs in mocoeffslist: for mocoeff in mocoeffs: wavefn = numpy.zeros(density.data.shape, "d") for bs in range(len(bfs)): data = numpy.zeros(density.data.shape, "d") for i, xval in enumerate(x): for j, yval in enumerate(y): tmp = [] for zval in z: tmp.append(bfs[bs].amp(xval, yval, zval)) data[i, j, :] = tmp data *= mocoeff[bs] wavefn += data density.data += wavefn ** 2 # TODO ROHF if len(mocoeffslist) == 1: density.data *= 2.0 return density
def gen_spectra(file_name, name, units='eV', thresh=9): data = ccread(file_name) energies, intensities = convertor(data.etenergies, 'cm-1', units), data.etoscs #intensities = abs(intensities) #print(len(energies)) #energies, intensities = energies[intensities > 10**-thresh], intensities[intensities > 10**-thresh] #print(len(energies)) #energies, intensities = energies[intensities > 10**-8], intensities[intensities > 10**-8] #print(len(energies)) #energies, intensities = energies[intensities > 10**-7], intensities[intensities > 10**-7] #print(len(energies)) #energies, intensities = energies[intensities > 10**-6], intensities[intensities > 10**-6] #print(len(energies)) #energies, intensities = energies[intensities > 10**-5], intensities[intensities > 10**-5] #print(len(energies)) #energies, intensities = energies[energies < 3*10**4], intensities[energies < 3*10**4] #print(len(energies)) s = Spectra(energies, intensities, name) return s
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" # extract the version number first if "Version" in line: self.metadata["package_version"] = line.split()[1] if line[1:19] == "ATOMIC COORDINATES": if not hasattr(self, "atomcoords"): self.atomcoords = [] atomcoords = [] atomnos = [] self.skip_lines(inputfile, ['line', 'line', 'line']) line = next(inputfile) while line.strip(): temp = line.strip().split() atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[3:6]]) # bohrs to angs atomnos.append(int(round(float(temp[2])))) line = next(inputfile) self.atomcoords.append(atomcoords) self.set_attribute('atomnos', atomnos) self.set_attribute('natom', len(self.atomnos)) # Use BASIS DATA to parse input for gbasis, aonames and atombasis. If symmetry is used, # the function number starts from 1 for each irrep (the irrep index comes after the dot). # # BASIS DATA # # Nr Sym Nuc Type Exponents Contraction coefficients # # 1.1 A 1 1s 71.616837 0.154329 # 13.045096 0.535328 # 3.530512 0.444635 # 2.1 A 1 1s 2.941249 -0.099967 # 0.683483 0.399513 # ... # if line[1:11] == "BASIS DATA": # We can do a sanity check with the header. self.skip_line(inputfile, 'blank') header = next(inputfile) assert header.split() == ["Nr", "Sym", "Nuc", "Type", "Exponents", "Contraction", "coefficients"] self.skip_line(inputfile, 'blank') aonames = [] atombasis = [[] for i in range(self.natom)] gbasis = [[] for i in range(self.natom)] while line.strip(): # We need to read the line at the start of the loop here, because the last function # will be added when a blank line signalling the end of the block is encountered. line = next(inputfile) # The formatting here can exhibit subtle differences, including the number of spaces # or indentation size. However, we will rely on explicit slices since not all components # are always available. In fact, components not being there has some meaning (see below). line_nr = line[1:6].strip() line_sym = line[7:9].strip() line_nuc = line[11:15].strip() line_type = line[16:22].strip() line_exp = line[25:38].strip() line_coeffs = line[38:].strip() # If a new function type is printed or the BASIS DATA block ends with a blank line, # then add the previous function to gbasis, except for the first function since # there was no preceeding one. When translating the Molpro function name to gbasis, # note that Molpro prints all components, but we want it only once, with the proper # shell type (S,P,D,F,G). Molpro names also differ between Cartesian/spherical representations. if (line_type and aonames) or line.strip() == "": # All the possible AO names are created with the class. The function should always # find a match in that dictionary, so we can check for that here and will need to # update the dict if something unexpected comes up. funcbasis = None for fb, names in self.atomic_orbital_names.items(): if functype in names: funcbasis = fb assert funcbasis # There is a separate basis function for each column of contraction coefficients. Since all # atomic orbitals for a subshell will have the same parameters, we can simply check if # the function tuple is already in gbasis[i] before adding it. for i in range(len(coefficients[0])): func = (funcbasis, []) for j in range(len(exponents)): func[1].append((exponents[j], coefficients[j][i])) if func not in gbasis[funcatom-1]: gbasis[funcatom-1].append(func) # If it is a new type, set up the variables for the next shell(s). An exception is symmetry functions, # which we want to copy from the previous function and don't have a new number on the line. For them, # we just want to update the nuclear index. if line_type: if line_nr: exponents = [] coefficients = [] functype = line_type funcatom = int(line_nuc) # Add any exponents and coefficients to lists. if line_exp and line_coeffs: funcexp = float(line_exp) funccoeffs = [float(s) for s in line_coeffs.split()] exponents.append(funcexp) coefficients.append(funccoeffs) # If the function number is present then add to atombasis and aonames, which is different from # adding to gbasis since it enumerates AOs rather than basis functions. The number counts functions # in each irrep from 1 and we could add up the functions for each irrep to get the global count, # but it is simpler to just see how many aonames we have already parsed. Any symmetry functions # are also printed, but they don't get numbers so they are nor parsed. if line_nr: element = self.table.element[self.atomnos[funcatom-1]] aoname = "%s%i_%s" % (element, funcatom, functype) aonames.append(aoname) funcnr = len(aonames) atombasis[funcatom-1].append(funcnr-1) self.set_attribute('aonames', aonames) self.set_attribute('atombasis', atombasis) self.set_attribute('gbasis', gbasis) if line[1:23] == "NUMBER OF CONTRACTIONS": nbasis = int(line.split()[3]) self.set_attribute('nbasis', nbasis) # Basis set name if line[1:8] == "Library": self.metadata["basis_set"] = line.split()[4] # This is used to signalize whether we are inside an SCF calculation. if line[1:8] == "PROGRAM" and line[14:18] == "-SCF": self.insidescf = True self.metadata["methods"].append("HF") # Use this information instead of 'SETTING ...', in case the defaults are standard. # Note that this is sometimes printed in each geometry optimization step. if line[1:20] == "NUMBER OF ELECTRONS": spinup = int(line.split()[3][:-1]) spindown = int(line.split()[4][:-1]) # Nuclear charges (atomnos) should be parsed by now. nuclear = numpy.sum(self.atomnos) charge = nuclear - spinup - spindown self.set_attribute('charge', charge) mult = spinup - spindown + 1 self.set_attribute('mult', mult) # Convergenve thresholds for SCF cycle, should be contained in a line such as: # CONVERGENCE THRESHOLDS: 1.00E-05 (Density) 1.40E-07 (Energy) if self.insidescf and line[1:24] == "CONVERGENCE THRESHOLDS:": if not hasattr(self, "scftargets"): self.scftargets = [] scftargets = list(map(float, line.split()[2::2])) self.scftargets.append(scftargets) # Usually two criteria, but save the names this just in case. self.scftargetnames = line.split()[3::2] # Read in the print out of the SCF cycle - for scfvalues. For RHF looks like: # ITERATION DDIFF GRAD ENERGY 2-EL.EN. DIPOLE MOMENTS DIIS # 1 0.000D+00 0.000D+00 -379.71523700 1159.621171 0.000000 0.000000 0.000000 0 # 2 0.000D+00 0.898D-02 -379.74469736 1162.389787 0.000000 0.000000 0.000000 1 # 3 0.817D-02 0.144D-02 -379.74635529 1162.041033 0.000000 0.000000 0.000000 2 # 4 0.213D-02 0.571D-03 -379.74658063 1162.159929 0.000000 0.000000 0.000000 3 # 5 0.799D-03 0.166D-03 -379.74660889 1162.144256 0.000000 0.000000 0.000000 4 if self.insidescf and line[1:10] == "ITERATION": if not hasattr(self, "scfvalues"): self.scfvalues = [] line = next(inputfile) energy = 0.0 scfvalues = [] while line.strip() != "": chomp = line.split() if chomp[0].isdigit(): ddiff = float(chomp[1].replace('D', 'E')) grad = float(chomp[2].replace('D', 'E')) newenergy = float(chomp[3]) ediff = newenergy - energy energy = newenergy # The convergence thresholds must have been read above. # Presently, we recognize MAX DENSITY and MAX ENERGY thresholds. numtargets = len(self.scftargetnames) values = [numpy.nan]*numtargets for n, name in zip(list(range(numtargets)), self.scftargetnames): if "ENERGY" in name.upper(): values[n] = ediff elif "DENSITY" in name.upper(): values[n] = ddiff scfvalues.append(values) try: line = next(inputfile) except StopIteration: self.logger.warning('File terminated before end of last SCF! Last gradient: {}'.format(grad)) break self.scfvalues.append(numpy.array(scfvalues)) # SCF result - RHF/UHF and DFT (RKS) energies. if (line[1:5] in ["!RHF", "!UHF", "!RKS"] and line[16:22].lower() == "energy"): if not hasattr(self, "scfenergies"): self.scfenergies = [] scfenergy = float(line.split()[4]) self.scfenergies.append(utils.convertor(scfenergy, "hartree", "eV")) # We are now done with SCF cycle (after a few lines). self.insidescf = False # MP2 energies. if line[1:5] == "!MP2": self.metadata["methods"].append("MP2") if not hasattr(self, 'mpenergies'): self.mpenergies = [] mp2energy = float(line.split()[-1]) mp2energy = utils.convertor(mp2energy, "hartree", "eV") self.mpenergies.append([mp2energy]) # MP2 energies if MP3 or MP4 is also calculated. if line[1:5] == "MP2:": self.metadata["methods"].append("MP2") if not hasattr(self, 'mpenergies'): self.mpenergies = [] mp2energy = float(line.split()[2]) mp2energy = utils.convertor(mp2energy, "hartree", "eV") self.mpenergies.append([mp2energy]) # MP3 (D) and MP4 (DQ or SDQ) energies. if line[1:8] == "MP3(D):": self.metadata["methods"].append("MP3") mp3energy = float(line.split()[2]) mp2energy = utils.convertor(mp3energy, "hartree", "eV") line = next(inputfile) self.mpenergies[-1].append(mp2energy) if line[1:9] == "MP4(DQ):": self.metadata["methods"].append("MP4") mp4energy = float(line.split()[2]) line = next(inputfile) if line[1:10] == "MP4(SDQ):": self.metadata["methods"].append("MP4") mp4energy = float(line.split()[2]) mp4energy = utils.convertor(mp4energy, "hartree", "eV") self.mpenergies[-1].append(mp4energy) # The CCSD program operates all closed-shel coupled cluster runs. if line[1:15] == "PROGRAM * CCSD": self.metadata["methods"].append("CCSD") if not hasattr(self, "ccenergies"): self.ccenergies = [] while line[1:20] != "Program statistics:": # The last energy (most exact) will be read last and thus saved. if line[1:5] == "!CCD" or line[1:6] == "!CCSD" or line[1:9] == "!CCSD(T)": ccenergy = float(line.split()[-1]) ccenergy = utils.convertor(ccenergy, "hartree", "eV") line = next(inputfile) self.ccenergies.append(ccenergy) # Read the occupancy (index of H**O s). # For restricted calculations, there is one line here. For unrestricted, two: # Final alpha occupancy: ... # Final beta occupancy: ... if line[1:17] == "Final occupancy:": self.homos = [int(line.split()[-1])-1] if line[1:23] == "Final alpha occupancy:": self.homos = [int(line.split()[-1])-1] line = next(inputfile) self.homos.append(int(line.split()[-1])-1) # Dipole is always printed on one line after the final RHF energy, and by default # it seems Molpro uses the origin as the reference point. if line.strip()[:13] == "Dipole moment": assert line.split()[2] == "/Debye" reference = [0.0, 0.0, 0.0] dipole = [float(d) for d in line.split()[-3:]] if not hasattr(self, 'moments'): self.moments = [reference, dipole] else: self.moments[1] == dipole # Static dipole polarizability. if line.strip() == "SCF dipole polarizabilities": if not hasattr(self, "polarizabilities"): self.polarizabilities = [] polarizability = [] self.skip_lines(inputfile, ['b', 'directions']) for _ in range(3): line = next(inputfile) polarizability.append(line.split()[1:]) self.polarizabilities.append(numpy.array(polarizability)) # Check for ELECTRON ORBITALS (canonical molecular orbitals). if line[1:18] == "ELECTRON ORBITALS" or self.electronorbitals: self._parse_orbitals(inputfile, line) # If the MATROP program was called appropriately, # the atomic obital overlap matrix S is printed. # The matrix is printed straight-out, ten elements in each row, both halves. # Note that is the entire matrix is not printed, then aooverlaps # will not have dimensions nbasis x nbasis. if line[1:9] == "MATRIX S": if not hasattr(self, "aooverlaps"): self.aooverlaps = [[]] self.skip_lines(inputfile, ['b', 'symblocklabel']) line = next(inputfile) while line.strip() != "": elements = [float(s) for s in line.split()] if len(self.aooverlaps[-1]) + len(elements) <= self.nbasis: self.aooverlaps[-1] += elements else: n = len(self.aooverlaps[-1]) + len(elements) - self.nbasis self.aooverlaps[-1] += elements[:-n] self.aooverlaps.append([]) self.aooverlaps[-1] += elements[-n:] line = next(inputfile) # Check for MCSCF natural orbitals. if line[1:17] == "NATURAL ORBITALS": self._parse_orbitals(inputfile, line) # Thresholds are printed only if the defaults are changed with GTHRESH. # In that case, we can fill geotargets with non-default values. # The block should look like this as of Molpro 2006.1: # THRESHOLDS: # ZERO = 1.00D-12 ONEINT = 1.00D-12 TWOINT = 1.00D-11 PREFAC = 1.00D-14 LOCALI = 1.00D-09 EORDER = 1.00D-04 # ENERGY = 0.00D+00 ETEST = 0.00D+00 EDENS = 0.00D+00 THRDEDEF= 1.00D-06 GRADIENT= 1.00D-02 STEP = 1.00D-03 # ORBITAL = 1.00D-05 CIVEC = 1.00D-05 COEFF = 1.00D-04 PRINTCI = 5.00D-02 PUNCHCI = 9.90D+01 OPTGRAD = 3.00D-04 # OPTENERG= 1.00D-06 OPTSTEP = 3.00D-04 THRGRAD = 2.00D-04 COMPRESS= 1.00D-11 VARMIN = 1.00D-07 VARMAX = 1.00D-03 # THRDOUB = 0.00D+00 THRDIV = 1.00D-05 THRRED = 1.00D-07 THRPSP = 1.00D+00 THRDC = 1.00D-10 THRCS = 1.00D-10 # THRNRM = 1.00D-08 THREQ = 0.00D+00 THRDE = 1.00D+00 THRREF = 1.00D-05 SPARFAC = 1.00D+00 THRDLP = 1.00D-07 # THRDIA = 1.00D-10 THRDLS = 1.00D-07 THRGPS = 0.00D+00 THRKEX = 0.00D+00 THRDIS = 2.00D-01 THRVAR = 1.00D-10 # THRLOC = 1.00D-06 THRGAP = 1.00D-06 THRLOCT = -1.00D+00 THRGAPT = -1.00D+00 THRORB = 1.00D-06 THRMLTP = 0.00D+00 # THRCPQCI= 1.00D-10 KEXTA = 0.00D+00 THRCOARS= 0.00D+00 SYMTOL = 1.00D-06 GRADTOL = 1.00D-06 THROVL = 1.00D-08 # THRORTH = 1.00D-08 GRID = 1.00D-06 GRIDMAX = 1.00D-03 DTMAX = 0.00D+00 if line[1:12] == "THRESHOLDS": self.skip_line(input, 'blank') line = next(inputfile) while line.strip(): if "OPTENERG" in line: start = line.find("OPTENERG") optenerg = line[start+10:start+20] if "OPTGRAD" in line: start = line.find("OPTGRAD") optgrad = line[start+10:start+20] if "OPTSTEP" in line: start = line.find("OPTSTEP") optstep = line[start+10:start+20] line = next(inputfile) self.geotargets = [optenerg, optgrad, optstep] # The optimization history is the source for geovlues: # # END OF GEOMETRY OPTIMIZATION. TOTAL CPU: 246.9 SEC # # ITER. ENERGY(OLD) ENERGY(NEW) DE GRADMAX GRADNORM GRADRMS STEPMAX STEPLEN STEPRMS # 1 -382.02936898 -382.04914450 -0.01977552 0.11354875 0.20127947 0.01183997 0.12972761 0.20171740 0.01186573 # 2 -382.04914450 -382.05059234 -0.00144784 0.03299860 0.03963339 0.00233138 0.05577169 0.06687650 0.00393391 # 3 -382.05059234 -382.05069136 -0.00009902 0.00694359 0.01069889 0.00062935 0.01654549 0.02016307 0.00118606 # ... # # The above is an exerpt from Molpro 2006, but it is a little bit different # for Molpro 2012, namely the 'END OF GEOMETRY OPTIMIZATION occurs after the # actual history list. It seems there is a another consistent line before the # history, but this might not be always true -- so this is a potential weak link. if line[1:30] == "END OF GEOMETRY OPTIMIZATION." or line.strip() == "Quadratic Steepest Descent - Minimum Search": # I think this is the trigger for convergence, and it shows up at the top in Molpro 2006. geometry_converged = line[1:30] == "END OF GEOMETRY OPTIMIZATION." self.skip_line(inputfile, 'blank') # Newer version of Molpro (at least for 2012) print and additional column # with the timing information for each step. Otherwise, the history looks the same. headers = next(inputfile).split() if not len(headers) in (10, 11): return # Although criteria can be changed, the printed format should not change. # In case it does, retrieve the columns for each parameter. index_ITER = headers.index('ITER.') index_THRENERG = headers.index('DE') index_THRGRAD = headers.index('GRADMAX') index_THRSTEP = headers.index('STEPMAX') line = next(inputfile) self.geovalues = [] while line.strip(): line = line.split() istep = int(line[index_ITER]) geovalues = [] geovalues.append(float(line[index_THRENERG])) geovalues.append(float(line[index_THRGRAD])) geovalues.append(float(line[index_THRSTEP])) self.geovalues.append(geovalues) line = next(inputfile) if line.strip() == "Freezing grid": line = next(inputfile) # The convergence trigger shows up somewhere at the bottom in Molpro 2012, # before the final stars. If convergence is not reached, there is an additional # line that can be checked for. This is a little tricky, though, since it is # not the last line... so bail out of the loop if convergence failure is detected. while "*****" not in line: line = next(inputfile) if line.strip() == "END OF GEOMETRY OPTIMIZATION.": geometry_converged = True if "No convergence" in line: geometry_converged = False break # Finally, deal with optdone, append the last step to it only if we had convergence. if not hasattr(self, 'optdone'): self.optdone = [] if geometry_converged: self.optdone.append(istep-1) # This block should look like this: # Normal Modes # # 1 Au 2 Bu 3 Ag 4 Bg 5 Ag # Wavenumbers [cm-1] 151.81 190.88 271.17 299.59 407.86 # Intensities [km/mol] 0.33 0.28 0.00 0.00 0.00 # Intensities [relative] 0.34 0.28 0.00 0.00 0.00 # CX1 0.00000 -0.01009 0.02577 0.00000 0.06008 # CY1 0.00000 -0.05723 -0.06696 0.00000 0.06349 # CZ1 -0.02021 0.00000 0.00000 0.11848 0.00000 # CX2 0.00000 -0.01344 0.05582 0.00000 -0.02513 # CY2 0.00000 -0.06288 -0.03618 0.00000 0.00349 # CZ2 -0.05565 0.00000 0.00000 0.07815 0.00000 # ... # Molpro prints low frequency modes in a subsequent section with the same format, # which also contains zero frequency modes, with the title: # Normal Modes of low/zero frequencies if line[1:13] == "Normal Modes": islow = (line[1:37] == "Normal Modes of low/zero frequencies") self.skip_line(inputfile, 'blank') # Each portion of five modes is followed by a single blank line. # The whole block is followed by an additional blank line. line = next(inputfile) while line.strip(): if line[1:25].isspace(): if not islow: # vibsyms not printed for low freq modes numbers = list(map(int, line.split()[::2])) vibsyms = line.split()[1::2] else: # give low freq modes an empty str as vibsym # note there could be other possibilities.. numbers = list(map(int, line.split())) vibsyms = ['']*len(numbers) if line[1:12] == "Wavenumbers": vibfreqs = list(map(float, line.strip().split()[2:])) if line[1:21] == "Intensities [km/mol]": vibirs = list(map(float, line.strip().split()[2:])) # There should always by 3xnatom displacement rows. if line[1:11].isspace() and line[13:25].strip().isdigit(): # There are a maximum of 5 modes per line. nmodes = len(line.split())-1 vibdisps = [] for i in range(nmodes): vibdisps.append([]) for n in range(self.natom): vibdisps[i].append([]) for i in range(nmodes): disp = float(line.split()[i+1]) vibdisps[i][0].append(disp) for i in range(self.natom*3 - 1): line = next(inputfile) iatom = (i+1)//3 for i in range(nmodes): disp = float(line.split()[i+1]) vibdisps[i][iatom].append(disp) line = next(inputfile) if not line.strip(): if not hasattr(self, "vibfreqs"): self.vibfreqs = [] if not hasattr(self, "vibsyms"): self.vibsyms = [] if not hasattr(self, "vibirs") and "vibirs" in dir(): self.vibirs = [] if not hasattr(self, "vibdisps") and "vibdisps" in dir(): self.vibdisps = [] if not islow: self.vibfreqs.extend(vibfreqs) self.vibsyms.extend(vibsyms) if "vibirs" in dir(): self.vibirs.extend(vibirs) if "vibdisps" in dir(): self.vibdisps.extend(vibdisps) else: nonzero = [f > 0 for f in vibfreqs] vibfreqs = [f for f in vibfreqs if f > 0] self.vibfreqs = vibfreqs + self.vibfreqs vibsyms = [vibsyms[i] for i in range(len(vibsyms)) if nonzero[i]] self.vibsyms = vibsyms + self.vibsyms if "vibirs" in dir(): vibirs = [vibirs[i] for i in range(len(vibirs)) if nonzero[i]] self.vibirs = vibirs + self.vibirs if "vibdisps" in dir(): vibdisps = [vibdisps[i] for i in range(len(vibdisps)) if nonzero[i]] self.vibdisps = vibdisps + self.vibdisps line = next(inputfile) if line[1:16] == "Force Constants": self.logger.info("Creating attribute hessian") self.hessian = [] line = next(inputfile) hess = [] tmp = [] while line.strip(): try: list(map(float, line.strip().split()[2:])) except: line = next(inputfile) line.strip().split()[1:] hess.extend([list(map(float, line.strip().split()[1:]))]) line = next(inputfile) lig = 0 while (lig == 0) or (len(hess[0]) > 1): tmp.append(hess.pop(0)) lig += 1 k = 5 while len(hess) != 0: tmp[k] += hess.pop(0) k += 1 if (len(tmp[k-1]) == lig): break if k >= lig: k = len(tmp[-1]) for l in tmp: self.hessian += l if line[1:14] == "Atomic Masses" and hasattr(self, "hessian"): line = next(inputfile) self.amass = list(map(float, line.strip().split()[2:])) while line.strip(): line = next(inputfile) self.amass += list(map(float, line.strip().split()[2:])) #1PROGRAM * POP (Mulliken population analysis) # # # Density matrix read from record 2100.2 Type=RHF/CHARGE (state 1.1) # # Population analysis by basis function type # # Unique atom s p d f g Total Charge # 2 C 3.11797 2.88497 0.00000 0.00000 0.00000 6.00294 - 0.00294 # 3 C 3.14091 2.91892 0.00000 0.00000 0.00000 6.05984 - 0.05984 # ... if line.strip() == "1PROGRAM * POP (Mulliken population analysis)": self.skip_lines(inputfile, ['b', 'b', 'density_source', 'b', 'func_type', 'b']) header = next(inputfile) icharge = header.split().index('Charge') charges = [] line = next(inputfile) while line.strip(): cols = line.split() charges.append(float(cols[icharge]+cols[icharge+1])) line = next(inputfile) if not hasattr(self, "atomcharges"): self.atomcharges = {} self.atomcharges['mulliken'] = charges if 'GRADIENT FOR STATE' in line: for _ in range(3): next(inputfile) grad = [] lines_read = 0 while lines_read < self.natom: line = next(inputfile) # Because molpro inserts an empty line every 50th atom. if line: grad.append([float(x) for x in line.split()[1:]]) lines_read += 1 if not hasattr(self, 'grads'): self.grads = [] self.grads.append(grad) if line[:25] == ' Variable memory released': self.metadata['success'] = True
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" # Extract the version number and optionally the revision number. if "version" in line: search = re.search(r"\sversion\s*(\d\.\d)", line) if search: self.metadata["package_version"] = search.groups()[0] if "Revision" in line: revision = line.split()[1] # Don't add revision information to the main package version for now. # if "package_version" in self.metadata: # package_version = "{}.r{}".format(self.metadata["package_version"], # revision) # self.metadata["package_version"] = package_version if line[1:22] == "total number of atoms": natom = int(line.split()[-1]) self.set_attribute('natom', natom) if line[3:44] == "convergence threshold in optimization run": # Assuming that this is only found in the case of OPTXYZ # (i.e. an optimization in Cartesian coordinates) self.geotargets = [float(line.split()[-2])] if line[32:61] == "largest component of gradient": # This is the geotarget in the case of OPTXYZ if not hasattr(self, "geovalues"): self.geovalues = [] self.geovalues.append([float(line.split()[4])]) if line[37:49] == "convergence?": # Get the geovalues and geotargets for OPTIMIZE if not hasattr(self, "geovalues"): self.geovalues = [] self.geotargets = [] geotargets = [] geovalues = [] for i in range(4): temp = line.split() geovalues.append(float(temp[2])) if not self.geotargets: geotargets.append(float(temp[-2])) line = next(inputfile) self.geovalues.append(geovalues) if not self.geotargets: self.geotargets = geotargets # This is the only place coordinates are printed in single point calculations. Note that # in the following fragment, the basis set selection is not always printed: # # ****************** # molecular geometry # ****************** # # **************************************** # * basis selected is sto sto3g * # **************************************** # # ******************************************************************************* # * * # * atom atomic coordinates number of * # * charge x y z shells * # * * # ******************************************************************************* # * * # * * # * c 6.0 0.0000000 -2.6361501 0.0000000 2 * # * 1s 2sp * # * * # * * # * c 6.0 0.0000000 2.6361501 0.0000000 2 * # * 1s 2sp * # * * # ... # if line.strip() == "molecular geometry": self.updateprogress(inputfile, "Coordinates") self.skip_lines(inputfile, ['s', 'b', 's']) line = next(inputfile) if "basis selected is" in line: self.skip_lines(inputfile, ['s', 'b', 's', 's']) self.skip_lines(inputfile, ['header1', 'header2', 's', 's']) atomnos = [] atomcoords = [] line = next(inputfile) while line.strip(): line = next(inputfile) if line.strip()[1:10].strip() and list(set(line.strip())) != ['*']: atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in line.split()[3:6]]) atomnos.append(int(round(float(line.split()[2])))) if not hasattr(self, "atomcoords"): self.atomcoords = [] self.atomcoords.append(atomcoords) self.set_attribute('atomnos', atomnos) # Each step of a geometry optimization will also print the coordinates: # # search 0 # ******************* # point 0 nuclear coordinates # ******************* # # x y z chg tag # ============================================================ # 0.0000000 -2.6361501 0.0000000 6.00 c # 0.0000000 2.6361501 0.0000000 6.00 c # .. # if line[40:59] == "nuclear coordinates": self.updateprogress(inputfile, "Coordinates") # We need not remember the first geometry in geometry optimizations, as this will # be already parsed from the "molecular geometry" section (see above). if not hasattr(self, 'firstnuccoords') or self.firstnuccoords: self.firstnuccoords = False return self.skip_lines(inputfile, ['s', 'b', 'colname', 'e']) atomcoords = [] atomnos = [] line = next(inputfile) while list(set(line.strip())) != ['=']: cols = line.split() atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in cols[0:3]]) atomnos.append(int(float(cols[3]))) line = next(inputfile) if not hasattr(self, "atomcoords"): self.atomcoords = [] self.atomcoords.append(atomcoords) self.set_attribute('atomnos', atomnos) # This is printed when a geometry optimization succeeds, after the last gradient of the energy. if line[40:62] == "optimization converged": self.skip_line(inputfile, 's') if not hasattr(self, 'optdone'): self.optdone = [] self.optdone.append(len(self.geovalues)-1) # This is apparently printed when a geometry optimization is not converged but the job ends. if "minimisation not converging" in line: self.skip_line(inputfile, 's') self.optdone = [] if line[1:32] == "total number of basis functions": nbasis = int(line.split()[-1]) self.set_attribute('nbasis', nbasis) while line.find("charge of molecule") < 0: line = next(inputfile) charge = int(line.split()[-1]) self.set_attribute('charge', charge) mult = int(next(inputfile).split()[-1]) self.set_attribute('mult', mult) alpha = int(next(inputfile).split()[-1])-1 beta = int(next(inputfile).split()[-1])-1 if self.mult == 1: self.homos = numpy.array([alpha], "i") else: self.homos = numpy.array([alpha, beta], "i") if line[37:69] == "s-matrix over gaussian basis set": self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") self.skip_lines(inputfile, ['d', 'b']) i = 0 while i < self.nbasis: self.updateprogress(inputfile, "Overlap") self.skip_lines(inputfile, ['b', 'b', 'header', 'b', 'b']) for j in range(self.nbasis): temp = list(map(float, next(inputfile).split()[1:])) self.aooverlaps[j, (0+i):(len(temp)+i)] = temp i += len(temp) if line[18:43] == 'EFFECTIVE CORE POTENTIALS': self.skip_line(inputfile, 'stars') self.coreelectrons = numpy.zeros(self.natom, 'i') line = next(inputfile) while line[15:46] != "*"*31: if line.find("for atoms ...") >= 0: atomindex = [] line = next(inputfile) while line.find("core charge") < 0: broken = line.split() atomindex.extend([int(x.split("-")[0]) for x in broken]) line = next(inputfile) charge = float(line.split()[4]) for idx in atomindex: self.coreelectrons[idx-1] = self.atomnos[idx-1] - charge line = next(inputfile) if line[3:27] == "Wavefunction convergence": self.scftarget = float(line.split()[-2]) self.scftargets = [] if line[11:22] == "normal mode": if not hasattr(self, "vibfreqs"): self.vibfreqs = [] self.vibirs = [] units = next(inputfile) xyz = next(inputfile) equals = next(inputfile) line = next(inputfile) while line != equals: temp = line.split() self.vibfreqs.append(float(temp[1])) self.vibirs.append(float(temp[-2])) line = next(inputfile) # Use the length of the vibdisps to figure out # how many rotations and translations to remove self.vibfreqs = self.vibfreqs[-len(self.vibdisps):] self.vibirs = self.vibirs[-len(self.vibdisps):] if line[44:73] == "normalised normal coordinates": self.skip_lines(inputfile, ['e', 'b', 'b']) self.vibdisps = [] freqnum = next(inputfile) while freqnum.find("=") < 0: self.skip_lines(inputfile, ['b', 'e', 'freqs', 'e', 'b', 'header', 'e']) p = [[] for x in range(9)] for i in range(len(self.atomnos)): brokenx = list(map(float, next(inputfile)[25:].split())) brokeny = list(map(float, next(inputfile)[25:].split())) brokenz = list(map(float, next(inputfile)[25:].split())) for j, x in enumerate(list(zip(brokenx, brokeny, brokenz))): p[j].append(x) self.vibdisps.extend(p) self.skip_lines(inputfile, ['b', 'b']) freqnum = next(inputfile) if line[26:36] == "raman data": self.vibramans = [] self.skip_lines(inputfile, ['s', 'b', 'header', 'b']) line = next(inputfile) while line[1] != "*": self.vibramans.append(float(line.split()[3])) self.skip_line(inputfile, 'blank') line = next(inputfile) # Use the length of the vibdisps to figure out # how many rotations and translations to remove self.vibramans = self.vibramans[-len(self.vibdisps):] if line[3:11] == "SCF TYPE": self.scftype = line.split()[-2] assert self.scftype in ['rhf', 'uhf', 'gvb'], "%s not one of 'rhf', 'uhf' or 'gvb'" % self.scftype if line[15:31] == "convergence data": if not hasattr(self, "scfvalues"): self.scfvalues = [] self.scftargets.append([self.scftarget]) # Assuming it does not change over time while line[1:10] != "="*9: line = next(inputfile) line = next(inputfile) tester = line.find("tester") # Can be in a different place depending assert tester >= 0 while line[1:10] != "="*9: # May be two or three lines (unres) line = next(inputfile) scfvalues = [] line = next(inputfile) while line.strip(): # e.g. **** recalulation of fock matrix on iteration 4 (examples/chap12/pyridine.out) if line[2:6] != "****": scfvalues.append([float(line[tester-5:tester+6])]) try: line = next(inputfile) except StopIteration: self.logger.warning('File terminated before end of last SCF! Last tester: {}'.format(line.split()[5])) break self.scfvalues.append(scfvalues) if line[10:22] == "total energy" and len(line.split()) == 3: if not hasattr(self, "scfenergies"): self.scfenergies = [] scfenergy = utils.convertor(float(line.split()[-1]), "hartree", "eV") self.scfenergies.append(scfenergy) # Total energies after Moller-Plesset corrections # Second order correction is always first, so its first occurance # triggers creation of mpenergies (list of lists of energies) # Further corrections are appended as found # Note: GAMESS-UK sometimes prints only the corrections, # so they must be added to the last value of scfenergies if line[10:32] == "mp2 correlation energy" or \ line[10:42] == "second order perturbation energy": if not hasattr(self, "mpenergies"): self.mpenergies = [] self.mpenergies.append([]) self.mp2correction = self.float(line.split()[-1]) self.mp2energy = self.scfenergies[-1] + self.mp2correction self.mpenergies[-1].append(utils.convertor(self.mp2energy, "hartree", "eV")) if line[10:41] == "third order perturbation energy": self.mp3correction = self.float(line.split()[-1]) self.mp3energy = self.mp2energy + self.mp3correction self.mpenergies[-1].append(utils.convertor(self.mp3energy, "hartree", "eV")) if line[40:59] == "molecular basis set": self.gbasis = [] line = next(inputfile) while line.find("contraction coefficients") < 0: line = next(inputfile) equals = next(inputfile) blank = next(inputfile) atomname = next(inputfile) basisregexp = re.compile("\d*(\D+)") # Get everything after any digits shellcounter = 1 while line != equals: gbasis = [] # Stores basis sets on one atom blank = next(inputfile) blank = next(inputfile) line = next(inputfile) shellno = int(line.split()[0]) shellgap = shellno - shellcounter shellsize = 0 while len(line.split()) != 1 and line != equals: if line.split(): shellsize += 1 coeff = {} # coefficients and symmetries for a block of rows while line.strip() and line != equals: temp = line.strip().split() # temp[1] may be either like (a) "1s" and "1sp", or (b) "s" and "sp" # See GAMESS-UK 7.0 distribution/examples/chap12/pyridine2_21m10r.out # for an example of the latter sym = basisregexp.match(temp[1]).groups()[0] assert sym in ['s', 'p', 'd', 'f', 'sp'], "'%s' not a recognized symmetry" % sym if sym == "sp": coeff.setdefault("S", []).append((float(temp[3]), float(temp[6]))) coeff.setdefault("P", []).append((float(temp[3]), float(temp[10]))) else: coeff.setdefault(sym.upper(), []).append((float(temp[3]), float(temp[6]))) line = next(inputfile) # either a blank or a continuation of the block if coeff: if sym == "sp": gbasis.append(('S', coeff['S'])) gbasis.append(('P', coeff['P'])) else: gbasis.append((sym.upper(), coeff[sym.upper()])) if line == equals: continue line = next(inputfile) # either the start of the next block or the start of a new atom or # the end of the basis function section (signified by a line of equals) numtoadd = 1 + (shellgap // shellsize) shellcounter = shellno + shellsize for x in range(numtoadd): self.gbasis.append(gbasis) if line[50:70] == "----- beta set -----": self.betamosyms = True self.betamoenergies = True self.betamocoeffs = True # betamosyms will be turned off in the next # SYMMETRY ASSIGNMENT section if line[31:50] == "SYMMETRY ASSIGNMENT": if not hasattr(self, "mosyms"): self.mosyms = [] multiple = {'a': 1, 'b': 1, 'e': 2, 't': 3, 'g': 4, 'h': 5} equals = next(inputfile) line = next(inputfile) while line != equals: # There may be one or two lines of title (compare mg10.out and duhf_1.out) line = next(inputfile) mosyms = [] line = next(inputfile) while line != equals: temp = line[25:30].strip() if temp[-1] == '?': # e.g. e? or t? or g? (see example/chap12/na7mg_uhf.out) # for two As, an A and an E, and two Es of the same energy respectively. t = line[91:].strip().split() for i in range(1, len(t), 2): for j in range(multiple[t[i][0]]): # add twice for 'e', etc. mosyms.append(self.normalisesym(t[i])) else: for j in range(multiple[temp[0]]): mosyms.append(self.normalisesym(temp)) # add twice for 'e', etc. line = next(inputfile) assert len(mosyms) == self.nmo, "mosyms: %d but nmo: %d" % (len(mosyms), self.nmo) if self.betamosyms: # Only append if beta (otherwise with IPRINT SCF # it will add mosyms for every step of a geo opt) self.mosyms.append(mosyms) self.betamosyms = False elif self.scftype == 'gvb': # gvb has alpha and beta orbitals but they are identical self.mosysms = [mosyms, mosyms] else: self.mosyms = [mosyms] if line[50:62] == "eigenvectors": # Mocoeffs...can get evalues from here too # (only if using FORMAT HIGH though will they all be present) if not hasattr(self, "mocoeffs"): self.aonames = [] aonames = [] minus = next(inputfile) mocoeffs = numpy.zeros((self.nmo, self.nbasis), "d") readatombasis = False if not hasattr(self, "atombasis"): self.atombasis = [] for i in range(self.natom): self.atombasis.append([]) readatombasis = True self.skip_lines(inputfile, ['b', 'b', 'evalues']) p = re.compile(r"\d+\s+(\d+)\s*(\w+) (\w+)") oldatomname = "DUMMY VALUE" mo = 0 while mo < self.nmo: self.updateprogress(inputfile, "Coefficients") self.skip_lines(inputfile, ['b', 'b', 'nums', 'b', 'b']) for basis in range(self.nbasis): line = next(inputfile) # Fill atombasis only first time around. if readatombasis: orbno = int(line[1:5])-1 atomno = int(line[6:9])-1 self.atombasis[atomno].append(orbno) if not self.aonames: pg = p.match(line[:18].strip()).groups() atomname = "%s%s%s" % (pg[1][0].upper(), pg[1][1:], pg[0]) if atomname != oldatomname: aonum = 1 oldatomname = atomname name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) if name in aonames: aonum += 1 name = "%s_%d%s" % (atomname, aonum, pg[2].upper()) aonames.append(name) temp = list(map(float, line[19:].split())) mocoeffs[mo:(mo+len(temp)), basis] = temp # Fill atombasis only first time around. readatombasis = False if not self.aonames: self.aonames = aonames line = next(inputfile) # blank line while not line.strip(): line = next(inputfile) evalues = line if evalues[:17].strip(): # i.e. if these aren't evalues break # Not all the MOs are present mo += len(temp) mocoeffs = mocoeffs[0:(mo+len(temp)), :] # In case some aren't present if self.betamocoeffs: self.mocoeffs.append(mocoeffs) else: self.mocoeffs = [mocoeffs] if line[7:12] == "irrep": ########## eigenvalues ########### # This section appears once at the start of a geo-opt and once at the end # unless IPRINT SCF is used (when it appears at every step in addition) if not hasattr(self, "moenergies"): self.moenergies = [] equals = next(inputfile) while equals[1:5] != "====": # May be one or two lines of title (compare duhf_1.out and mg10.out) equals = next(inputfile) moenergies = [] line = next(inputfile) if not line.strip(): # May be a blank line here (compare duhf_1.out and mg10.out) line = next(inputfile) while line.strip() and line != equals: # May end with a blank or equals temp = line.strip().split() moenergies.append(utils.convertor(float(temp[2]), "hartree", "eV")) line = next(inputfile) self.nmo = len(moenergies) if self.betamoenergies: self.moenergies.append(moenergies) self.betamoenergies = False elif self.scftype == 'gvb': self.moenergies = [moenergies, moenergies] else: self.moenergies = [moenergies] # The dipole moment is printed by default at the beginning of the wavefunction analysis, # but the value is in atomic units, so we need to convert to Debye. It seems pretty # evident that the reference point is the origin (0,0,0) which is also the center # of mass after reorientation at the beginning of the job, although this is not # stated anywhere (would be good to check). # # ********************* # wavefunction analysis # ********************* # # commence analysis at 24.61 seconds # # dipole moments # # # nuclear electronic total # # x 0.0000000 0.0000000 0.0000000 # y 0.0000000 0.0000000 0.0000000 # z 0.0000000 0.0000000 0.0000000 # if line.strip() == "dipole moments": # In older version there is only one blank line before the header, # and newer version there are two. self.skip_line(inputfile, 'blank') line = next(inputfile) if not line.strip(): line = next(inputfile) self.skip_line(inputfile, 'blank') dipole = [] for i in range(3): line = next(inputfile) dipole.append(float(line.split()[-1])) reference = [0.0, 0.0, 0.0] dipole = utils.convertor(numpy.array(dipole), "ebohr", "Debye") if not hasattr(self, 'moments'): self.moments = [reference, dipole] else: assert self.moments[1] == dipole # Net atomic charges are not printed at all, it seems, # but you can get at them from nuclear charges and # electron populations, which are printed like so: # # --------------------------------------- # mulliken and lowdin population analyses # --------------------------------------- # # ----- total gross population in aos ------ # # 1 1 c s 1.99066 1.98479 # 2 1 c s 1.14685 1.04816 # ... # # ----- total gross population on atoms ---- # # 1 c 6.0 6.00446 5.99625 # 2 c 6.0 6.00446 5.99625 # 3 c 6.0 6.07671 6.04399 # ... if line[10:49] == "mulliken and lowdin population analyses": if not hasattr(self, "atomcharges"): self.atomcharges = {} while not "total gross population on atoms" in line: line = next(inputfile) self.skip_line(inputfile, 'blank') line = next(inputfile) mulliken, lowdin = [], [] while line.strip(): nuclear = float(line.split()[2]) mulliken.append(nuclear - float(line.split()[3])) lowdin.append(nuclear - float(line.split()[4])) line = next(inputfile) self.atomcharges["mulliken"] = mulliken self.atomcharges["lowdin"] = lowdin # ----- spinfree UHF natural orbital occupations ----- # # 2.0000000 2.0000000 2.0000000 2.0000000 2.0000000 2.0000000 2.0000000 # # 2.0000000 2.0000000 2.0000000 2.0000000 2.0000000 1.9999997 1.9999997 # ... if "natural orbital occupations" in line: occupations = [] self.skip_line(inputfile, "blank") line = inputfile.next() while line.strip(): occupations += map(float, line.split()) self.skip_line(inputfile, "blank") line = inputfile.next() self.set_attribute('nooccnos', occupations) if line[:33] == ' end of G A M E S S program at': self.metadata['success'] = True
def main(args): import os import sys from utils import make_file_iterator import cclib import numpy as np if args.actually_plot: import matplotlib as mpl mpl.use('Agg') import matplotlib.pyplot as plt matches_ccman1 = ( 'DOING EOM-CCSD CALCULATIONS', 'GENUINE CIS CODE', 'GENUINE CISD CODE', 'GENUINE CISDT CODE' ) matches_correlated_gs_energies_cdman = ( # Shows up in RI-CIS(D) calculations. 'RIMP2 total energy', # Shows up in SOS-CIS(D) and SOS-CIS(D0) calculations. 'Total SOS-MP2 energy', # Shows up in CIS(D) calculations (without RI). 'Total ground state energy' ) matches_orca_cis = ( 'CIS-EXCITED STATES', 'CIS EXCITED STATES' ) if args.actually_plot: fig, ax = plt.subplots() cmap = plt.cm.get_cmap('nipy_spectral') njobs = len(args.inputfile) ax.set_color_cycle([cmap(i) for i in np.linspace(0, 1, njobs)]) for inputfilename in args.inputfile: # We aren't parsing the files, since they might fail; just # trying to determine which program they came from for now. job = cclib.io.ccopen(inputfilename) stub = os.path.splitext(inputfilename)[0] inputfile = make_file_iterator(inputfilename) unrestricted = True do_quartet = False if type(job) == cclib.parser.qchemparser.QChem: print('Q-Chem:', stub) for line in inputfile: # Are we using a ROHF reference? if ' restricted ' in line: unrestricted = False # This is the RHF/ROHF/UHF ground state energy. if 'Total energy in the final basis set' in line: energy_gs = float(line.split()[-1]) # Do we want a correlated ground state energy instead? # (RI-MP2, MP2, CCSD, ...) if args.correlated_gs_energy: # Runs that call cdman will match here. if any(match in line for match in matches_correlated_gs_energies_cdman): energy_gs = float(line.split()[-2]) # Runs that call ccman/ccman2 will match here. if 'ccsd total energy' in line.lower(): energy_gs = float(line.split()[-1]) if 'Doublet and Quartet excitation energies requested' in line: do_quartet = True if 'CIS Excitation Energies' in line: energies_es = qchem_get_cis_energies(inputfile, do_quartet) if 'TDDFT/TDA Excitation Energies' in line: energies_es = qchem_get_cis_energies(inputfile) if line.strip() == 'CIS(D) Excitation Energies': energies_es = qchem_get_cisd_energies(inputfile, energy_gs) if line.strip() == 'RI-CIS(D) Excitation Energies': energies_es = qchem_get_ricisd_energies(inputfile) if line.strip() == 'SOS-CIS(D) Excitation Energies': energies_es = qchem_get_ricisd_energies(inputfile) if line.strip() == 'SOS-CIS(D0) Excitation Energies': energies_es = qchem_get_cis_energies(inputfile, unrestricted) if 'Solving for EOM-CCSD' in line: energies_es = qchem_get_eom_energies_ccman2(inputfile) if any(line.strip() == match for match in matches_ccman1): energies_es = qchem_get_eom_energies_ccman1(inputfile) elif type(job) == cclib.parser.orcaparser.ORCA: print('ORCA:', stub) for line in inputfile: if 'Total Energy' in line: energy_gs = float(line.split()[3]) if 'Generation of triplets' in line: do_triplet = on_off_bool(line.split()[-1]) if any(match in line for match in matches_orca_cis): energies_es = orca_get_cis_ex_energies(inputfile) if 'TD-DFT/TDA EXCITED STATES' in line: energies_es = orca_get_cis_ex_energies(inputfile) elif type(job) == cclib.parser.psiparser.Psi: print('Psi:', stub) pass else: sys.exit() print('Ground state energy (hartree):') print(energy_gs) try: print('Excited state energies:') print(energies_es) if type(job) == cclib.parser.orcaparser.ORCA: excitation_energies = energies_es else: excitation_energies = [convertor(energy_es - energy_gs, 'hartree', 'eV') for energy_es in energies_es] # print('Excitation energies:') # print(excitation_energies) nstates = len(excitation_energies) states = range(1, nstates + 1) if args.actually_plot: ax.plot(states, excitation_energies[:nstates], label=stub, marker='o') except: print("Something's wrong!") if args.actually_plot: ax.set_xlabel('excited state #') if args.correlated_gs_energy: ax.set_ylabel(r'$E_{\mathrm{state}} - E_{\mathrm{GS}}$ (eV)') else: ax.set_ylabel(r'$E_{\mathrm{state}} - E_{\mathrm{GS}}^{\mathrm{HF}}$ (eV)') ax.set_xticks(states) # Which one do I choose? Nobody knows! I can never remember. ax.set_xlim(1, nstates) # ax.set_xbound(1, nstates) ax.legend(loc='best', fancybox=True, framealpha=0.50, fontsize='x-small') if args.correlated_gs_energy: fig.savefig('plot_corr.pdf', bbox_inches='tight') else: fig.savefig('plot.pdf', bbox_inches='tight')
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" if "Start Module: gateway" in line: self.gateway_module_count += 1 if self.gateway_module_count > 1: return # Extract the version number and optionally the Git tag and hash. if "version" in line: match = re.search(r"\s{2,}version\s(\d*\.\d*)", line) if match: package_version = match.groups()[0] self.metadata["package_version"] = package_version # Don't add revision information to the main package version for now. if "tag" in line: tag = line.split()[-1] if "build" in line: match = re.search(r"\*\s*build\s(\S*)\s*\*", line) if match: revision = match.groups()[0] ## This section is present when executing &GATEWAY. # ++ Molecular structure info: # ------------------------- # ************************************************ # **** Cartesian Coordinates / Bohr, Angstrom **** # ************************************************ # Center Label x y z x y z # 1 C1 0.526628 -2.582937 0.000000 0.278679 -1.366832 0.000000 # 2 C2 2.500165 -0.834760 0.000000 1.323030 -0.441736 0.000000 if line[25:63] == 'Cartesian Coordinates / Bohr, Angstrom': if not hasattr(self, 'atomnos'): self.atomnos = [] self.skip_lines(inputfile, ['stars', 'blank', 'header']) line = next(inputfile) atomelements = [] atomcoords = [] while line.strip() not in ('', '--'): sline = line.split() atomelement = sline[1].rstrip(string.digits).title() atomelements.append(atomelement) atomcoords.append(list(map(float, sline[5:]))) line = next(inputfile) self.append_attribute('atomcoords', atomcoords) if self.atomnos == []: self.atomnos = [self.table.number[ae.title()] for ae in atomelements] if not hasattr(self, 'natom'): self.set_attribute('natom', len(self.atomnos)) ## This section is present when executing &SCF. # ++ Orbital specifications: # ----------------------- # Symmetry species 1 # Frozen orbitals 0 # Occupied orbitals 3 # Secondary orbitals 77 # Deleted orbitals 0 # Total number of orbitals 80 # Number of basis functions 80 # -- if line[:29] == '++ Orbital specifications:': self.skip_lines(inputfile, ['dashes', 'blank']) line = next(inputfile) symmetry_count = 1 while not line.startswith('--'): if line.strip().startswith('Symmetry species'): symmetry_count = int(line.split()[-1]) if line.strip().startswith('Total number of orbitals'): nmos = line.split()[-symmetry_count:] self.set_attribute('nmo', sum(map(int, nmos))) if line.strip().startswith('Number of basis functions'): nbasis = line.split()[-symmetry_count:] self.set_attribute('nbasis', sum(map(int, nbasis))) line = next(inputfile) if line.strip().startswith(('Molecular charge', 'Total molecular charge')): self.set_attribute('charge', int(float(line.split()[-1]))) # ++ Molecular charges: # ------------------ # Mulliken charges per centre and basis function type # --------------------------------------------------- # C1 # 1s 2.0005 # 2s 2.0207 # 2px 0.0253 # 2pz 0.1147 # 2py 1.8198 # *s -0.0215 # *px 0.0005 # *pz 0.0023 # *py 0.0368 # *d2+ 0.0002 # *d1+ 0.0000 # *d0 0.0000 # *d1- 0.0000 # *d2- 0.0000 # *f3+ 0.0000 # *f2+ 0.0001 # *f1+ 0.0000 # *f0 0.0001 # *f1- 0.0001 # *f2- 0.0000 # *f3- 0.0003 # *g4+ 0.0000 # *g3+ 0.0000 # *g2+ 0.0000 # *g1+ 0.0000 # *g0 0.0000 # *g1- 0.0000 # *g2- 0.0000 # *g3- 0.0000 # *g4- 0.0000 # Total 6.0000 # N-E 0.0000 # Total electronic charge= 6.000000 # Total charge= 0.000000 #-- if line[:24] == '++ Molecular charges:': atomcharges = [] while line[6:29] != 'Total electronic charge': line = next(inputfile) if line[6:9] == 'N-E': atomcharges.extend(map(float, line.split()[1:])) # Molcas only performs Mulliken population analysis. self.set_attribute('atomcharges', {'mulliken': atomcharges}) # Ensure the charge printed here is identical to the # charge printed before entering the SCF. self.skip_line(inputfile, 'blank') line = next(inputfile) assert line[6:30] == 'Total charge=' if hasattr(self, 'charge'): assert int(float(line.split()[2])) == self.charge # This section is present when executing &SCF # This section parses the total SCF Energy. # ***************************************************************************************************************************** # * * # * SCF/KS-DFT Program, Final results * # * * # * * # * * # * Final Results * # * * # ***************************************************************************************************************************** # :: Total SCF energy -37.6045426484 if line[:22] == ':: Total SCF energy' or line[:25] == ':: Total KS-DFT energy': if not hasattr(self, 'scfenergies'): self.scfenergies = [] scfenergy = float(line.split()[-1]) self.scfenergies.append(utils.convertor(scfenergy, 'hartree', 'eV')) ## Parsing the scftargets in this section # ++ Optimization specifications: # ---------------------------- # SCF Algorithm: Conventional # Minimized density differences are used # Number of density matrices in core 9 # Maximum number of NDDO SCF iterations 400 # Maximum number of HF SCF iterations 400 # Threshold for SCF energy change 0.10E-08 # Threshold for density matrix 0.10E-03 # Threshold for Fock matrix 0.15E-03 # Threshold for linear dependence 0.10E-08 # Threshold at which DIIS is turned on 0.15E+00 # Threshold at which QNR/C2DIIS is turned on 0.75E-01 # Threshold for Norm(delta) (QNR/C2DIIS) 0.20E-04 if line[:34] == '++ Optimization specifications:': self.skip_lines(inputfile, ['d', 'b']) line = next(inputfile) if line.strip().startswith('SCF'): scftargets = [] self.skip_lines(inputfile, ['Minimized', 'Number', 'Maximum', 'Maximum']) lines = [next(inputfile) for i in range(7)] targets = [ 'Threshold for SCF energy change', 'Threshold for density matrix', 'Threshold for Fock matrix', 'Threshold for Norm(delta)', ] for y in targets: scftargets.extend([float(x.split()[-1]) for x in lines if y in x]) self.append_attribute('scftargets', scftargets) # ++ Convergence information # SCF iterations: Energy and convergence statistics # # Iter Tot. SCF One-electron Two-electron Energy Max Dij or Max Fij DNorm TNorm AccCon Time # Energy Energy Energy Change Delta Norm in Sec. # 1 -36.83817703 -50.43096166 13.59278464 0.00E+00 0.16E+00* 0.27E+01* 0.30E+01 0.33E+02 NoneDa 0. # 2 -36.03405202 -45.74525152 9.71119950 0.80E+00* 0.14E+00* 0.93E-02* 0.26E+01 0.43E+01 Damp 0. # 3 -37.08936118 -48.41536598 11.32600480 -0.11E+01* 0.12E+00* 0.91E-01* 0.97E+00 0.16E+01 Damp 0. # 4 -37.31610460 -50.54103969 13.22493509 -0.23E+00* 0.11E+00* 0.96E-01* 0.72E+00 0.27E+01 Damp 0. # 5 -37.33596239 -49.47021484 12.13425245 -0.20E-01* 0.59E-01* 0.59E-01* 0.37E+00 0.16E+01 Damp 0. # ... # Convergence after 26 Macro Iterations # -- if line[46:91] == 'iterations: Energy and convergence statistics': self.skip_line(inputfile, 'blank') while line.split() != ['Energy', 'Energy', 'Energy', 'Change', 'Delta', 'Norm', 'in', 'Sec.']: line = next(inputfile) iteration_regex = ("^([0-9]+)" # Iter "( [ \-0-9]*\.[0-9]{6,9})" # Tot. SCF Energy "( [ \-0-9]*\.[0-9]{6,9})" # One-electron Energy "( [ \-0-9]*\.[0-9]{6,9})" # Two-electron Energy "( [ \-0-9]*\.[0-9]{2}E[\-\+][0-9]{2}\*?)" # Energy Change "( [ \-0-9]*\.[0-9]{2}E[\-\+][0-9]{2}\*?)" # Max Dij or Delta Norm "( [ \-0-9]*\.[0-9]{2}E[\-\+][0-9]{2}\*?)" # Max Fij "( [ \-0-9]*\.[0-9]{2}E[\-\+][0-9]{2}\*?)" # DNorm "( [ \-0-9]*\.[0-9]{2}E[\-\+][0-9]{2}\*?)" # TNorm "( [ A-Za-z0-9]*)" # AccCon "( [ \.0-9]*)$") # Time in Sec. scfvalues = [] line = next(inputfile) while not line.strip().startswith("Convergence"): match = re.match(iteration_regex, line.strip()) if match: groups = match.groups() cols = [g.strip() for g in match.groups()] cols = [c.replace('*', '') for c in cols] energy = float(cols[4]) density = float(cols[5]) fock = float(cols[6]) dnorm = float(cols[7]) scfvalues.append([energy, density, fock, dnorm]) if line.strip() == "--": self.logger.warning('File terminated before end of last SCF!') break line = next(inputfile) self.append_attribute('scfvalues', scfvalues) # Harmonic frequencies in cm-1 # # IR Intensities in km/mol # # 1 2 3 4 5 6 # # Frequency: i60.14 i57.39 128.18 210.06 298.24 309.65 # # Intensity: 3.177E-03 2.129E-06 4.767E-01 2.056E-01 6.983E-07 1.753E-07 # Red. mass: 2.42030 2.34024 2.68044 3.66414 2.61721 3.34904 # # C1 x -0.00000 0.00000 0.00000 -0.05921 0.00000 -0.06807 # C1 y 0.00001 -0.00001 -0.00001 0.00889 0.00001 -0.02479 # C1 z -0.03190 0.04096 -0.03872 0.00001 -0.12398 -0.00002 # C2 x -0.00000 0.00001 0.00000 -0.06504 0.00000 -0.03487 # C2 y 0.00000 -0.00000 -0.00000 0.01045 0.00001 -0.05659 # C2 z -0.03703 -0.03449 -0.07269 0.00000 -0.07416 -0.00001 # C3 x -0.00000 0.00001 0.00000 -0.06409 -0.00001 0.05110 # C3 y -0.00000 0.00001 0.00000 0.00152 0.00000 -0.03263 # C3 z -0.03808 -0.08037 -0.07267 -0.00001 0.07305 0.00000 # ... # H20 y 0.00245 -0.00394 0.03215 0.03444 -0.10424 -0.10517 # H20 z 0.00002 -0.00001 0.00000 -0.00000 -0.00000 0.00000 # # # # ++ Thermochemistry if line[1:29] == 'Harmonic frequencies in cm-1': self.skip_line(inputfile, 'blank') line = next(inputfile) while 'Thermochemistry' not in line: if 'Frequency:' in line: if not hasattr(self, 'vibfreqs'): self.vibfreqs = [] vibfreqs = [float(i.replace('i', '-')) for i in line.split()[1:]] self.vibfreqs.extend(vibfreqs) if 'Intensity:' in line: if not hasattr(self, 'vibirs'): self.vibirs = [] vibirs = map(float, line.split()[1:]) self.vibirs.extend(vibirs) if 'Red.' in line: self.skip_line(inputfile, 'blank') line = next(inputfile) if not hasattr(self, 'vibdisps'): self.vibdisps = [] disps = [] for n in range(3*self.natom): numbers = [float(s) for s in line[17:].split()] # The atomindex should start at 0 instead of 1. atomindex = int(re.search(r'\d+$', line.split()[0]).group()) - 1 numbermodes = len(numbers) if len(disps) == 0: # Appends empty array of the following # dimensions (numbermodes, natom, 0) to disps. for mode in range(numbermodes): disps.append([[] for x in range(0, self.natom)]) for mode in range(numbermodes): disps[mode][atomindex].append(numbers[mode]) line = next(inputfile) self.vibdisps.extend(disps) line = next(inputfile) ## Parsing thermochemistry attributes here # ++ Thermochemistry # # ********************* # * * # * THERMOCHEMISTRY * # * * # ********************* # # Mass-centered Coordinates (Angstrom): # *********************************************************** # ... # ***************************************************** # Temperature = 0.00 Kelvin, Pressure = 1.00 atm # ----------------------------------------------------- # Molecular Partition Function and Molar Entropy: # q/V (M**-3) S(kcal/mol*K) # Electronic 0.100000D+01 0.000 # Translational 0.100000D+01 0.000 # Rotational 0.100000D+01 2.981 # Vibrational 0.100000D+01 0.000 # TOTAL 0.100000D+01 2.981 # # Thermal contributions to INTERNAL ENERGY: # Electronic 0.000 kcal/mol 0.000000 au. # Translational 0.000 kcal/mol 0.000000 au. # Rotational 0.000 kcal/mol 0.000000 au. # Vibrational 111.885 kcal/mol 0.178300 au. # TOTAL 111.885 kcal/mol 0.178300 au. # # Thermal contributions to # ENTHALPY 111.885 kcal/mol 0.178300 au. # GIBBS FREE ENERGY 111.885 kcal/mol 0.178300 au. # # Sum of energy and thermal contributions # INTERNAL ENERGY -382.121931 au. # ENTHALPY -382.121931 au. # GIBBS FREE ENERGY -382.121931 au. # ----------------------------------------------------- # ... # ENTHALPY -382.102619 au. # GIBBS FREE ENERGY -382.179819 au. # ----------------------------------------------------- # -- # # ++ Isotopic shifts: if line[4:19] == 'THERMOCHEMISTRY': temperature_values = [] pressure_values = [] entropy_values = [] internal_energy_values = [] enthalpy_values = [] free_energy_values = [] while 'Isotopic' not in line: if line[1:12] == 'Temperature': temperature_values.append(float(line.split()[2])) pressure_values.append(float(line.split()[6])) if line[1:48] == 'Molecular Partition Function and Molar Entropy:': while 'TOTAL' not in line: line = next(inputfile) entropy_values.append(utils.convertor(float(line.split()[2]), 'kcal/mol', 'hartree')) if line[1:40] == 'Sum of energy and thermal contributions': internal_energy_values.append(float(next(inputfile).split()[2])) enthalpy_values.append(float(next(inputfile).split()[1])) free_energy_values.append(float(next(inputfile).split()[3])) line = next(inputfile) # When calculations for more than one temperature value are # performed, the values corresponding to room temperature (298.15 K) # are returned and if no calculations are performed for 298.15 K, then # the values corresponding last temperature value are returned. index = -1 if 298.15 in temperature_values: index = temperature_values.index(298.15) self.set_attribute('temperature', temperature_values[index]) if len(temperature_values) > 1: self.logger.warning('More than 1 values of temperature found') self.set_attribute('pressure', pressure_values[index]) if len(pressure_values) > 1: self.logger.warning('More than 1 values of pressure found') self.set_attribute('entropy', entropy_values[index]) if len(entropy_values) > 1: self.logger.warning('More than 1 values of entropy found') self.set_attribute('enthalpy', enthalpy_values[index]) if len(enthalpy_values) > 1: self.logger.warning('More than 1 values of enthalpy found') self.set_attribute('freeenergy', free_energy_values[index]) if len(free_energy_values) > 1: self.logger.warning('More than 1 values of freeenergy found') ## Parsing Geometrical Optimization attributes in this section. # ++ Slapaf input parameters: # ------------------------ # # Max iterations: 2000 # Convergence test a la Schlegel. # Convergence criterion on gradient/para.<=: 0.3E-03 # Convergence criterion on step/parameter<=: 0.3E-03 # Convergence criterion on energy change <=: 0.0E+00 # Max change of an internal coordinate: 0.30E+00 # ... # ... # ********************************************************************************************************************** # * Energy Statistics for Geometry Optimization * # ********************************************************************************************************************** # Energy Grad Grad Step Estimated Geom Hessian # Iter Energy Change Norm Max Element Max Element Final Energy Update Update Index # 1 -382.30023222 0.00000000 0.107221 0.039531 nrc047 0.085726 nrc047 -382.30533799 RS-RFO None 0 # 2 -382.30702964 -0.00679742 0.043573 0.014908 nrc001 0.068195 nrc001 -382.30871333 RS-RFO BFGS 0 # 3 -382.30805348 -0.00102384 0.014883 0.005458 nrc010 -0.020973 nrc001 -382.30822089 RS-RFO BFGS 0 # ... # ... # 18 -382.30823419 -0.00000136 0.001032 0.000100 nrc053 0.012319 nrc053 -382.30823452 RS-RFO BFGS 0 # 19 -382.30823198 0.00000221 0.001051 -0.000092 nrc054 0.066565 nrc053 -382.30823822 RS-RFO BFGS 0 # 20 -382.30820252 0.00002946 0.001132 -0.000167 nrc021 -0.064003 nrc053 -382.30823244 RS-RFO BFGS 0 # # +----------------------------------+----------------------------------+ # + Cartesian Displacements + Gradient in internals + # + Value Threshold Converged? + Value Threshold Converged? + # +-----+----------------------------------+----------------------------------+ # + RMS + 5.7330E-02 1.2000E-03 No + 1.6508E-04 3.0000E-04 Yes + # +-----+----------------------------------+----------------------------------+ # + Max + 1.2039E-01 1.8000E-03 No + 1.6711E-04 4.5000E-04 Yes + # +-----+----------------------------------+----------------------------------+ if 'Convergence criterion on energy change' in line: self.energy_threshold = float(line.split()[6]) # If energy change threshold equals zero, # then energy change is not a criteria for convergence. if self.energy_threshold == 0: self.energy_threshold = numpy.inf if 'Energy Statistics for Geometry Optimization' in line: if not hasattr(self, 'geovalues'): self.geovalues = [] self.skip_lines(inputfile, ['stars', 'header']) line = next(inputfile) assert 'Iter Energy Change Norm' in line # A variable keeping track of ongoing iteration. iter_number = len(self.geovalues) + 1 # Iterate till blank line. while line.split() != []: for i in range(iter_number): line = next(inputfile) self.geovalues.append([float(line.split()[2])]) line = next(inputfile) # Along with energy change, RMS and Max values of change in # Cartesian Diaplacement and Gradients are used as optimization # criteria. self.skip_lines(inputfile, ['border', 'header', 'header', 'border']) line = next(inputfile) assert '+ RMS +' in line line_rms = line.split() line = next(inputfile) line_max = next(inputfile).split() if not hasattr(self, 'geotargets'): # The attribute geotargets is an array consisting of the following # values: [Energy threshold, Max Gradient threshold, RMS Gradient threshold, \ # Max Displacements threshold, RMS Displacements threshold]. max_gradient_threshold = float(line_max[8]) rms_gradient_threshold = float(line_rms[8]) max_displacement_threshold = float(line_max[4]) rms_displacement_threshold = float(line_rms[4]) self.geotargets = [self.energy_threshold, max_gradient_threshold, rms_gradient_threshold, max_displacement_threshold, rms_displacement_threshold] max_gradient_change = float(line_max[7]) rms_gradient_change = float(line_rms[7]) max_displacement_change = float(line_max[3]) rms_displacement_change = float(line_rms[3]) self.geovalues[iter_number - 1].extend([max_gradient_change, rms_gradient_change, max_displacement_change, rms_displacement_change]) # ********************************************************* # * Nuclear coordinates for the next iteration / Angstrom * # ********************************************************* # ATOM X Y Z # C1 0.235560 -1.415847 0.012012 # C2 1.313797 -0.488199 0.015149 # C3 1.087050 0.895510 0.014200 # ... # ... # H19 -0.021327 -4.934915 -0.029355 # H20 -1.432030 -3.721047 -0.039835 # # -- if 'Nuclear coordinates for the next iteration / Angstrom' in line: self.skip_lines(inputfile, ['s', 'header']) line = next(inputfile) atomcoords = [] while line.split() != []: atomcoords.append([float(c) for c in line.split()[1:]]) line = next(inputfile) if len(atomcoords) == self.natom: self.atomcoords.append(atomcoords) else: self.logger.warning( "Parsed coordinates not consistent with previous, skipping. " "This could be due to symmetry being turned on during the job. " "Length was %i, now found %i. New coordinates: %s" % (len(self.atomcoords[-1]), len(atomcoords), str(atomcoords))) # ********************************************************************************************************************** # * Energy Statistics for Geometry Optimization * # ********************************************************************************************************************** # Energy Grad Grad Step Estimated Geom Hessian # Iter Energy Change Norm Max Element Max Element Final Energy Update Update Index # 1 -382.30023222 0.00000000 0.107221 0.039531 nrc047 0.085726 nrc047 -382.30533799 RS-RFO None 0 # ... # ... # 23 -382.30823115 -0.00000089 0.001030 0.000088 nrc053 0.000955 nrc053 -382.30823118 RS-RFO BFGS 0 # # +----------------------------------+----------------------------------+ # + Cartesian Displacements + Gradient in internals + # + Value Threshold Converged? + Value Threshold Converged? + # +-----+----------------------------------+----------------------------------+ # + RMS + 7.2395E-04 1.2000E-03 Yes + 2.7516E-04 3.0000E-04 Yes + # +-----+----------------------------------+----------------------------------+ # + Max + 1.6918E-03 1.8000E-03 Yes + 8.7768E-05 4.5000E-04 Yes + # +-----+----------------------------------+----------------------------------+ # # Geometry is converged in 23 iterations to a Minimum Structure if 'Geometry is converged' in line: if not hasattr(self, 'optdone'): self.optdone = [] self.optdone.append(len(self.atomcoords)) # ********************************************************* # * Nuclear coordinates of the final structure / Angstrom * # ********************************************************* # ATOM X Y Z # C1 0.235547 -1.415838 0.012193 # C2 1.313784 -0.488201 0.015297 # C3 1.087036 0.895508 0.014333 # ... # ... # H19 -0.021315 -4.934913 -0.029666 # H20 -1.431994 -3.721026 -0.041078 if 'Nuclear coordinates of the final structure / Angstrom' in line: self.skip_lines(inputfile, ['s', 'header']) line = next(inputfile) atomcoords = [] while line.split() != []: atomcoords.append([float(c) for c in line.split()[1:]]) line = next(inputfile) if len(atomcoords) == self.natom: self.atomcoords.append(atomcoords) else: self.logger.error( 'Number of atoms (%d) in parsed atom coordinates ' 'is smaller than previously (%d), possibly due to ' 'symmetry. Ignoring these coordinates.' % (len(atomcoords), self.natom)) # All orbitals with orbital energies smaller than E(LUMO)+0.5 are printed # # ++ Molecular orbitals: # ------------------- # # Title: RKS-DFT orbitals # # Molecular orbitals for symmetry species 1: a # # Orbital 1 2 3 4 5 6 7 8 9 10 # Energy -10.0179 -10.0179 -10.0075 -10.0075 -10.0066 -10.0066 -10.0056 -10.0055 -9.9919 -9.9919 # Occ. No. 2.0000 2.0000 2.0000 2.0000 2.0000 2.0000 2.0000 2.0000 2.0000 2.0000 # # 1 C1 1s -0.6990 0.6989 0.0342 0.0346 0.0264 -0.0145 -0.0124 -0.0275 -0.0004 -0.0004 # 2 C1 2s -0.0319 0.0317 -0.0034 -0.0033 -0.0078 0.0034 0.0041 0.0073 -0.0002 -0.0002 # ... # ... # 58 H18 1s 0.2678 # 59 H19 1s -0.2473 # 60 H20 1s 0.1835 # -- if '++ Molecular orbitals:' in line: self.skip_lines(inputfile, ['d', 'b']) line = next(inputfile) # We don't currently support parsing natural orbitals or active space orbitals. if 'Natural orbitals' not in line and "Pseudonatural" not in line: self.skip_line(inputfile, 'b') # Symmetry is not currently supported, so this line can have one form. while 'Molecular orbitals for symmetry species 1: a' not in line.strip(): line = next(inputfile) # Symmetry is not currently supported, so this line can have one form. if line.strip() != 'Molecular orbitals for symmetry species 1: a': return line = next(inputfile) moenergies = [] homos = 0 mocoeffs = [] while line[:2] != '--': line = next(inputfile) if line.strip().startswith('Orbital'): orbital_index = line.split()[1:] for i in orbital_index: mocoeffs.append([]) if 'Energy' in line: energies = [utils.convertor(float(x), 'hartree', 'eV') for x in line.split()[1:]] moenergies.extend(energies) if 'Occ. No.' in line: for i in line.split()[2:]: if float(i) != 0: homos += 1 aonames = [] tokens = line.split() if tokens and tokens[0] == '1': while tokens and tokens[0] != '--': aonames.append("{atom}_{orbital}".format(atom=tokens[1], orbital=tokens[2])) info = tokens[3:] j = 0 for i in orbital_index: mocoeffs[int(i)-1].append(float(info[j])) j += 1 line = next(inputfile) tokens = line.split() self.set_attribute('aonames', aonames) if len(moenergies) != self.nmo: moenergies.extend([numpy.nan for x in range(self.nmo - len(moenergies))]) self.append_attribute('moenergies', moenergies) if not hasattr(self, 'homos'): self.homos = [] self.homos.extend([homos-1]) while len(mocoeffs) < self.nmo: nan_array = [numpy.nan for i in range(self.nbasis)] mocoeffs.append(nan_array) self.append_attribute('mocoeffs', mocoeffs) ## Parsing MP energy from the &MBPT2 module. # Conventional algorithm used... # # SCF energy = -74.9644564043 a.u. # Second-order correlation energy = -0.0364237923 a.u. # # Total energy = -75.0008801966 a.u. # Reference weight ( Cref**2 ) = 0.98652 # # :: Total MBPT2 energy -75.0008801966 # # # Zeroth-order energy (E0) = -36.8202538520 a.u. # # Shanks-type energy S1(E) = -75.0009150108 a.u. if 'Total MBPT2 energy' in line: mpenergies = [] mpenergies.append(utils.convertor(self.float(line.split()[4]), 'hartree', 'eV')) if not hasattr(self, 'mpenergies'): self.mpenergies = [] self.mpenergies.append(mpenergies) # Parsing data ccenergies from &CCSDT module. # --- Start Module: ccsdt at Thu Jul 26 14:03:23 2018 --- # # ()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()() # # &CCSDT # ... # ... # 14 -75.01515915 -0.05070274 -0.00000029 # 15 -75.01515929 -0.05070289 -0.00000014 # 16 -75.01515936 -0.05070296 -0.00000007 # Convergence after 17 Iterations # # # Total energy (diff) : -75.01515936 -0.00000007 # Correlation energy : -0.0507029554992 if 'Start Module: ccsdt' in line: self.skip_lines(inputfile, ['b', '()', 'b']) line = next(inputfile) if '&CCSDT' in line: while not line.strip().startswith('Total energy (diff)'): line = next(inputfile) ccenergies = utils.convertor(self.float(line.split()[4]), 'hartree', 'eV') if not hasattr(self, 'ccenergies'): self.ccenergies= [] self.ccenergies.append(ccenergies) # ++ Primitive basis info: # --------------------- # # # ***************************************************** # ******** Primitive Basis Functions (Valence) ******** # ***************************************************** # # # Basis set:C.AUG-CC-PVQZ......... # # Type # s # No. Exponent Contraction Coefficients # 1 0.339800000D+05 0.000091 -0.000019 0.000000 0.000000 0.000000 0.000000 # 2 0.508900000D+04 0.000704 -0.000151 0.000000 0.000000 0.000000 0.000000 # ... # ... # 29 0.424000000D+00 0.000000 1.000000 # # Number of primitives 93 # Number of basis functions 80 # # -- if line.startswith('++ Primitive basis info:'): self.skip_lines(inputfile, ['d', 'b', 'b', 's', 'header', 's', 'b']) line = next(inputfile) gbasis_array = [] while '--' not in line and '****' not in line: if 'Basis set:' in line: basis_element_patterns = re.findall('Basis set:([A-Za-z]{1,2})\.', line) assert len(basis_element_patterns) == 1 basis_element = basis_element_patterns[0].title() gbasis_array.append((basis_element, [])) if 'Type' in line: line = next(inputfile) shell_type = line.split()[0].upper() self.skip_line(inputfile, 'headers') line = next(inputfile) exponents = [] coefficients = [] func_array = [] while line.split(): exponents.append(self.float(line.split()[1])) coefficients.append([self.float(i) for i in line.split()[2:]]) line = next(inputfile) for i in range(len(coefficients[0])): func_tuple = (shell_type, []) for iexp, exp in enumerate(exponents): coeff = coefficients[iexp][i] if coeff != 0: func_tuple[1].append((exp, coeff)) gbasis_array[-1][1].append(func_tuple) line = next(inputfile) atomsymbols = [self.table.element[atomno] for atomno in self.atomnos] self.gbasis = [[] for i in range(self.natom)] for element, gbasis in gbasis_array: mask = [element == possible_element for possible_element in atomsymbols] indices = [i for (i, x) in enumerate(mask) if x] for index in indices: self.gbasis[index] = gbasis # ++ Basis set information: # ---------------------- # ... # Basis set label: MO.ECP.HAY-WADT.5S6P4D.3S3P2D.14E-LANL2DZ..... # # Electronic valence basis set: # ------------------ # Associated Effective Charge 14.000000 au # Associated Actual Charge 42.000000 au # Nuclear Model: Point charge # ... # # Effective Core Potential specification: # ======================================= # # Label Cartesian Coordinates / Bohr # # MO 0.0006141610 -0.0006141610 0.0979067106 # -- if '++ Basis set information:' in line: self.core_array = [] basis_element = None ncore = 0 while line[:2] != '--': if 'Basis set label' in line: try: basis_element = line.split()[3].split('.')[0] basis_element = basis_element[0] + basis_element[1:].lower() except: self.logger.warning('Basis set label is missing!') basis_element = '' if 'valence basis set:' in line.lower(): self.skip_line(inputfile, 'd') line = next(inputfile) if 'Associated Effective Charge' in line: effective_charge = float(line.split()[3]) actual_charge = float(next(inputfile).split()[3]) element = self.table.element[int(actual_charge)] ncore = int(actual_charge - effective_charge) if basis_element: assert basis_element == element else: basis_element = element if basis_element and ncore: self.core_array.append((basis_element, ncore)) basis_element = '' ncore = 0 line = next(inputfile)
def integrate_square(self): boxvol = (self.spacing[0] * self.spacing[1] * self.spacing[2] * convertor(1, "Angstrom", "bohr")**3) return sum(self.data.ravel()**2) * boxvol
parser = argparse.ArgumentParser() parser.add_argument('outputfilename', nargs='+') args = parser.parse_args() return args if __name__ == '__main__': args = getargs() scfenergies = [] for outputfilename in args.outputfilename: if 'cfour' in outputfilename.lower(): program = 'CFOUR' scfenergy = get_energy_nocclib(outputfilename, 'E(SCF)=', 1) else: job = ccopen(outputfilename) program = program_names[type(job)] data = job.parse() scfenergy = convertor(data.scfenergies[0], 'eV', 'hartree') scfenergies.append((program, outputfilename, scfenergy)) scfenergies = sorted(scfenergies, key=lambda x: x[2]) for (program, outputfilename, scfenergy) in scfenergies: print(scfenergy, program, outputfilename)
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" # If a file contains multiple calculations, currently we want to print a warning # and skip to the end of the file, since cclib parses only the main system, which # is usually the largest. Here we test this by checking if scftargets has already # been parsed when another INPUT FILE segment is found, although this might # not always be the best indicator. if line.strip() == "(INPUT FILE)" and hasattr(self, "scftargets"): self.logger.warning("Skipping remaining calculations") inputfile.seek(0, 2) return # We also want to check to make sure we aren't parsing "Create" jobs, # which normally come before the calculation we actually want to parse. if line.strip() == "(INPUT FILE)": while True: self.updateprogress(inputfile, "Unsupported Information", self.fupdate) line = next(inputfile) if line.strip() == "(INPUT FILE)" else None if line and not line[:6] in ("Create", "create"): break line = next(inputfile) version_searchstr = "Amsterdam Density Functional (ADF)" if version_searchstr in line: startidx = line.index(version_searchstr) + len(version_searchstr) trimmed_line = line[startidx:].strip()[:-1] # The package version is normally a year with revision # number (such as 2013.01), but it may also be a random # string (such as a version control branch name). match = re.search(r"([\d\.]{4,7})", trimmed_line) if match: package_version = match.groups()[0] self.metadata["package_version"] = package_version else: # This isn't as well-defined, but the field shouldn't # be left empty. self.metadata["package_version"] = trimmed_line.strip() # In ADF 2014.01, there are (INPUT FILE) messages, so we need to use just # the lines that start with 'Create' and run until the title or something # else we are sure is is the calculation proper. It would be good to combine # this with the previous block, if possible. if line[:6] == "Create": while line[:5] != "title" and "NO TITLE" not in line: line = inputfile.next() if line[1:10] == "Symmetry:": info = line.split() if info[1] == "NOSYM": self.nosymflag = True # Use this to read the subspecies of irreducible representations. # It will be a list, with each element representing one irrep. if line.strip() == "Irreducible Representations, including subspecies": self.skip_line(inputfile, 'dashes') self.irreps = [] line = next(inputfile) while line.strip() != "": self.irreps.append(line.split()) line = next(inputfile) if line[4:13] == 'Molecule:': info = line.split() if info[1] == 'UNrestricted': self.unrestrictedflag = True if line[1:6] == "ATOMS": # Find the number of atoms and their atomic numbers # Also extract the starting coordinates (for a GeoOpt anyway) # and the atommasses (previously called vibmasses) self.updateprogress(inputfile, "Attributes", self.cupdate) self.atomcoords = [] self.skip_lines(inputfile, ['header1', 'header2', 'header3']) atomnos = [] atommasses = [] atomcoords = [] coreelectrons = [] line = next(inputfile) while len(line) > 2: # ensure that we are reading no blank lines info = line.split() element = info[1].split('.')[0] atomnos.append(self.table.number[element]) atomcoords.append(list(map(float, info[2:5]))) coreelectrons.append(int(float(info[5]) - float(info[6]))) atommasses.append(float(info[7])) line = next(inputfile) self.atomcoords.append(atomcoords) self.set_attribute('natom', len(atomnos)) self.set_attribute('atomnos', atomnos) self.set_attribute('atommasses', atommasses) self.set_attribute('coreelectrons', coreelectrons) if line[1:10] == "FRAGMENTS": header = next(inputfile) self.frags = [] self.fragnames = [] line = next(inputfile) while len(line) > 2: # ensure that we are reading no blank lines info = line.split() if len(info) == 7: # fragment name is listed here self.fragnames.append("%s_%s" % (info[1], info[0])) self.frags.append([]) self.frags[-1].append(int(info[2]) - 1) elif len(info) == 5: # add atoms into last fragment self.frags[-1].append(int(info[0]) - 1) line = next(inputfile) # Extract charge if line[1:11] == "Net Charge": charge = int(line.split()[2]) self.set_attribute('charge', charge) line = next(inputfile) if len(line.strip()): # Spin polar: 1 (Spin_A minus Spin_B electrons) # (Not sure about this for higher multiplicities) mult = int(line.split()[2]) + 1 else: mult = 1 self.set_attribute('mult', mult) if line[1:22] == "S C F U P D A T E S": # find targets for SCF convergence if not hasattr(self, "scftargets"): self.scftargets = [] self.skip_lines(inputfile, ['e', 'b', 'numbers']) line = next(inputfile) self.SCFconv = float(line.split()[-1]) line = next(inputfile) self.sconv2 = float(line.split()[-1]) # In ADF 2013, the default numerical integration method is fuzzy cells, # although it used to be Voronoi polyhedra. Both methods apparently set # the accint parameter, although the latter does so indirectly, based on # a 'grid quality' setting. This is translated into accint using a # dictionary with values taken from the documentation. if "Numerical Integration : Voronoi Polyhedra (Te Velde)" in line: self.integration_method = "voronoi_polyhedra" if line[1:27] == 'General Accuracy Parameter': # Need to know the accuracy of the integration grid to # calculate the scftarget...note that it changes with time self.accint = float(line.split()[-1]) if "Numerical Integration : Fuzzy Cells (Becke)" in line: self.integration_method = 'fuzzy_cells' if line[1:19] == "Becke grid quality": self.grid_quality = line.split()[-1] quality2accint = { 'BASIC': 2.0, 'NORMAL': 4.0, 'GOOD': 6.0, 'VERYGOOD': 8.0, 'EXCELLENT': 10.0, } self.accint = quality2accint[self.grid_quality] # Half of the atomic orbital overlap matrix is printed since it is symmetric, # but this requires "PRINT Smat" to be in the input. There are extra blank lines # at the end of the block, which are used to terminate the parsing. # # ====== smat # # column 1 2 3 4 # row # 1 1.00000000000000E+00 # 2 2.43370854175315E-01 1.00000000000000E+00 # 3 0.00000000000000E+00 0.00000000000000E+00 1.00000000000000E+00 # ... # if "====== smat" in line: # Initialize the matrix with Nones so we can easily check all has been parsed. overlaps = [[None] * self.nbasis for i in range(self.nbasis)] self.skip_line(inputfile, 'blank') line = inputfile.next() while line.strip(): colline = line assert colline.split()[0] == "column" columns = [int(i) for i in colline.split()[1:]] rowline = inputfile.next() assert rowline.strip() == "row" line = inputfile.next() while line.strip(): i = int(line.split()[0]) vals = [float(col) for col in line.split()[1:]] for j, o in enumerate(vals): k = columns[j] overlaps[k-1][i-1] = o overlaps[i-1][k-1] = o line = inputfile.next() line = inputfile.next() # Now all values should be parsed, and so no Nones remaining. assert all([all([x is not None for x in ao]) for ao in overlaps]) self.set_attribute('aooverlaps', overlaps) if line[1:11] == "CYCLE 1": self.updateprogress(inputfile, "QM convergence", self.fupdate) newlist = [] line = next(inputfile) if not hasattr(self, "geovalues"): # This is the first SCF cycle self.scftargets.append([self.sconv2*10, self.sconv2]) elif self.finalgeometry in [self.GETLAST, self.NOMORE]: # This is the final SCF cycle self.scftargets.append([self.SCFconv*10, self.SCFconv]) else: # This is an intermediate SCF cycle in a geometry optimization, # in which case the SCF convergence target needs to be derived # from the accint parameter. For Voronoi polyhedra integration, # accint is printed and parsed. For fuzzy cells, it can be inferred # from the grid quality setting, as is done somewhere above. if self.accint: oldscftst = self.scftargets[-1][1] grdmax = self.geovalues[-1][1] scftst = max(self.SCFconv, min(oldscftst, grdmax/30, 10**(-self.accint))) self.scftargets.append([scftst*10, scftst]) while line.find("SCF CONVERGED") == -1 and line.find("SCF not fully converged, result acceptable") == -1 and line.find("SCF NOT CONVERGED") == -1: if line[4:12] == "SCF test": if not hasattr(self, "scfvalues"): self.scfvalues = [] info = line.split() newlist.append([float(info[4]), abs(float(info[6]))]) try: line = next(inputfile) except StopIteration: # EOF reached? self.logger.warning("SCF did not converge, so attributes may be missing") break if line.find("SCF not fully converged, result acceptable") > 0: self.logger.warning("SCF not fully converged, results acceptable") if line.find("SCF NOT CONVERGED") > 0: self.logger.warning("SCF did not converge! moenergies and mocoeffs are unreliable") if hasattr(self, "scfvalues"): self.scfvalues.append(newlist) # Parse SCF energy for SP calcs from bonding energy decomposition section. # It seems ADF does not print it earlier for SP calculations. # Geometry optimization runs also print this, and we want to parse it # for them, too, even if it repeats the last "Geometry Convergence Tests" # section (but it's usually a bit different). if line[:21] == "Total Bonding Energy:": if not hasattr(self, "scfenergies"): self.scfenergies = [] energy = utils.convertor(float(line.split()[3]), "hartree", "eV") self.scfenergies.append(energy) if line[51:65] == "Final Geometry": self.finalgeometry = self.GETLAST # Get the coordinates from each step of the GeoOpt. if line[1:24] == "Coordinates (Cartesian)" and self.finalgeometry in [self.NOTFOUND, self.GETLAST]: self.skip_lines(inputfile, ['e', 'b', 'title', 'title', 'd']) atomcoords = [] line = next(inputfile) while list(set(line.strip())) != ['-']: atomcoords.append(list(map(float, line.split()[5:8]))) line = next(inputfile) if not hasattr(self, "atomcoords"): self.atomcoords = [] self.atomcoords.append(atomcoords) # Don't get any more coordinates in this case. # KML: I think we could combine this with optdone (see below). if self.finalgeometry == self.GETLAST: self.finalgeometry = self.NOMORE # There have been some changes in the format of the geometry convergence information, # and this is how it is printed in older versions (2007.01 unit tests). # # ========================== # Geometry Convergence Tests # ========================== # # Energy old : -5.14170647 # new : -5.15951374 # # Convergence tests: # (Energies in hartree, Gradients in hartree/angstr or radian, Lengths in angstrom, Angles in degrees) # # Item Value Criterion Conv. Ratio # ------------------------------------------------------------------------- # change in energy -0.01780727 0.00100000 NO 0.00346330 # gradient max 0.03219530 0.01000000 NO 0.30402650 # gradient rms 0.00858685 0.00666667 NO 0.27221261 # cart. step max 0.07674971 0.01000000 NO 0.75559435 # cart. step rms 0.02132310 0.00666667 NO 0.55335378 # if line[1:27] == 'Geometry Convergence Tests': if not hasattr(self, "geotargets"): self.geovalues = [] self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0], "d") if not hasattr(self, "scfenergies"): self.scfenergies = [] self.skip_lines(inputfile, ['e', 'b']) energies_old = next(inputfile) energies_new = next(inputfile) self.scfenergies.append(utils.convertor(float(energies_new.split()[-1]), "hartree", "eV")) self.skip_lines(inputfile, ['b', 'convergence', 'units', 'b', 'header', 'd']) values = [] for i in range(5): temp = next(inputfile).split() self.geotargets[i] = float(temp[-3]) values.append(float(temp[-4])) self.geovalues.append(values) # This is to make geometry optimization always have the optdone attribute, # even if it is to be empty for unconverged runs. if not hasattr(self, 'optdone'): self.optdone = [] # After the test, there is a message if the search is converged: # # *************************************************************************************************** # Geometry CONVERGED # *************************************************************************************************** # if line.strip() == "Geometry CONVERGED": self.skip_line(inputfile, 'stars') self.optdone.append(len(self.geovalues) - 1) # Here is the corresponding geometry convergence info from the 2013.01 unit test. # Note that the step number is given, which it will be prudent to use in an assertion. # #---------------------------------------------------------------------- #Geometry Convergence after Step 3 (Hartree/Angstrom,Angstrom) #---------------------------------------------------------------------- #current energy -5.16274478 Hartree #energy change -0.00237544 0.00100000 F #constrained gradient max 0.00884999 0.00100000 F #constrained gradient rms 0.00249569 0.00066667 F #gradient max 0.00884999 #gradient rms 0.00249569 #cart. step max 0.03331296 0.01000000 F #cart. step rms 0.00844037 0.00666667 F if line[:31] == "Geometry Convergence after Step": stepno = int(line.split()[4]) # This is to make geometry optimization always have the optdone attribute, # even if it is to be empty for unconverged runs. if not hasattr(self, 'optdone'): self.optdone = [] # The convergence message is inline in this block, not later as it was before. if "** CONVERGED **" in line: if not hasattr(self, 'optdone'): self.optdone = [] self.optdone.append(len(self.geovalues) - 1) self.skip_line(inputfile, 'dashes') current_energy = next(inputfile) energy_change = next(inputfile) constrained_gradient_max = next(inputfile) constrained_gradient_rms = next(inputfile) gradient_max = next(inputfile) gradient_rms = next(inputfile) cart_step_max = next(inputfile) cart_step_rms = next(inputfile) if not hasattr(self, "scfenergies"): self.scfenergies = [] energy = utils.convertor(float(current_energy.split()[-2]), "hartree", "eV") self.scfenergies.append(energy) if not hasattr(self, "geotargets"): self.geotargets = numpy.array([0.0, 0.0, 0.0, 0.0, 0.0], "d") self.geotargets[0] = float(energy_change.split()[-2]) self.geotargets[1] = float(constrained_gradient_max.split()[-2]) self.geotargets[2] = float(constrained_gradient_rms.split()[-2]) self.geotargets[3] = float(cart_step_max.split()[-2]) self.geotargets[4] = float(cart_step_rms.split()[-2]) if not hasattr(self, "geovalues"): self.geovalues = [] self.geovalues.append([]) self.geovalues[-1].append(float(energy_change.split()[-3])) self.geovalues[-1].append(float(constrained_gradient_max.split()[-3])) self.geovalues[-1].append(float(constrained_gradient_rms.split()[-3])) self.geovalues[-1].append(float(cart_step_max.split()[-3])) self.geovalues[-1].append(float(cart_step_rms.split()[-3])) if line.find('Orbital Energies, per Irrep and Spin') > 0 and not hasattr(self, "mosyms") and self.nosymflag and not self.unrestrictedflag: #Extracting orbital symmetries and energies, homos for nosym case #Should only be for restricted case because there is a better text block for unrestricted and nosym self.mosyms = [[]] self.moenergies = [[]] self.skip_lines(inputfile, ['e', 'header', 'd', 'label']) line = next(inputfile) info = line.split() if not info[0] == '1': self.logger.warning("MO info up to #%s is missing" % info[0]) #handle case where MO information up to a certain orbital are missing while int(info[0]) - 1 != len(self.moenergies[0]): self.moenergies[0].append(99999) self.mosyms[0].append('A') homoA = None while len(line) > 10: info = line.split() self.mosyms[0].append('A') self.moenergies[0].append(utils.convertor(float(info[2]), 'hartree', 'eV')) if info[1] == '0.000' and not hasattr(self, 'homos'): self.set_attribute('homos', [len(self.moenergies[0]) - 2]) line = next(inputfile) self.moenergies = [numpy.array(self.moenergies[0], "d")] if line[1:29] == 'Orbital Energies, both Spins' and not hasattr(self, "mosyms") and self.nosymflag and self.unrestrictedflag: #Extracting orbital symmetries and energies, homos for nosym case #should only be here if unrestricted and nosym self.mosyms = [[], []] moenergies = [[], []] self.skip_lines(inputfile, ['d', 'b', 'header', 'd']) homoa = 0 homob = None line = next(inputfile) while len(line) > 5: info = line.split() if info[2] == 'A': self.mosyms[0].append('A') moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) if info[3] != '0.00': homoa = len(moenergies[0]) - 1 elif info[2] == 'B': self.mosyms[1].append('A') moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) if info[3] != '0.00': homob = len(moenergies[1]) - 1 else: print(("Error reading line: %s" % line)) line = next(inputfile) self.moenergies = [numpy.array(x, "d") for x in moenergies] self.set_attribute('homos', [homoa, homob]) # Extracting orbital symmetries and energies, homos. if line[1:29] == 'Orbital Energies, all Irreps' and not hasattr(self, "mosyms"): self.symlist = {} self.mosyms = [[]] self.moenergies = [[]] self.skip_lines(inputfile, ['e', 'b', 'header', 'd']) homoa = None homob = None #multiple = {'E':2, 'T':3, 'P':3, 'D':5} # The above is set if there are no special irreps names = [irrep[0].split(':')[0] for irrep in self.irreps] counts = [len(irrep) for irrep in self.irreps] multiple = dict(list(zip(names, counts))) irrepspecies = {} for n in range(len(names)): indices = list(range(counts[n])) subspecies = self.irreps[n] irrepspecies[names[n]] = dict(list(zip(indices, subspecies))) line = next(inputfile) while line.strip(): info = line.split() if len(info) == 5: # this is restricted #count = multiple.get(info[0][0],1) count = multiple.get(info[0], 1) for repeat in range(count): # i.e. add E's twice, T's thrice self.mosyms[0].append(self.normalisesym(info[0])) self.moenergies[0].append(utils.convertor(float(info[3]), 'hartree', 'eV')) sym = info[0] if count > 1: # add additional sym label sym = self.normalisedegenerates(info[0], repeat, ndict=irrepspecies) try: self.symlist[sym][0].append(len(self.moenergies[0])-1) except KeyError: self.symlist[sym] = [[]] self.symlist[sym][0].append(len(self.moenergies[0])-1) if info[2] == '0.00' and not hasattr(self, 'homos'): self.homos = [len(self.moenergies[0]) - (count + 1)] # count, because need to handle degenerate cases line = next(inputfile) elif len(info) == 6: # this is unrestricted if len(self.moenergies) < 2: # if we don't have space, create it self.moenergies.append([]) self.mosyms.append([]) # count = multiple.get(info[0][0], 1) count = multiple.get(info[0], 1) if info[2] == 'A': for repeat in range(count): # i.e. add E's twice, T's thrice self.mosyms[0].append(self.normalisesym(info[0])) self.moenergies[0].append(utils.convertor(float(info[4]), 'hartree', 'eV')) sym = info[0] if count > 1: # add additional sym label sym = self.normalisedegenerates(info[0], repeat) try: self.symlist[sym][0].append(len(self.moenergies[0])-1) except KeyError: self.symlist[sym] = [[], []] self.symlist[sym][0].append(len(self.moenergies[0])-1) if info[3] == '0.00' and homoa is None: homoa = len(self.moenergies[0]) - (count + 1) # count because degenerate cases need to be handled if info[2] == 'B': for repeat in range(count): # i.e. add E's twice, T's thrice self.mosyms[1].append(self.normalisesym(info[0])) self.moenergies[1].append(utils.convertor(float(info[4]), 'hartree', 'eV')) sym = info[0] if count > 1: # add additional sym label sym = self.normalisedegenerates(info[0], repeat) try: self.symlist[sym][1].append(len(self.moenergies[1])-1) except KeyError: self.symlist[sym] = [[], []] self.symlist[sym][1].append(len(self.moenergies[1])-1) if info[3] == '0.00' and homob is None: homob = len(self.moenergies[1]) - (count + 1) line = next(inputfile) else: # different number of lines print(("Error", info)) if len(info) == 6: # still unrestricted, despite being out of loop self.set_attribute('homos', [homoa, homob]) self.moenergies = [numpy.array(x, "d") for x in self.moenergies] # Section on extracting vibdisps # Also contains vibfreqs, but these are extracted in the # following section (see below) if line[1:28] == "Vibrations and Normal Modes": self.vibdisps = [] self.skip_lines(inputfile, ['e', 'b', 'header', 'header', 'b', 'b']) freqs = next(inputfile) while freqs.strip() != "": minus = next(inputfile) p = [[], [], []] for i in range(len(self.atomnos)): broken = list(map(float, next(inputfile).split()[1:])) for j in range(0, len(broken), 3): p[j//3].append(broken[j:j+3]) self.vibdisps.extend(p[:(len(broken)//3)]) self.skip_lines(inputfile, ['b', 'b']) freqs = next(inputfile) self.vibdisps = numpy.array(self.vibdisps, "d") if line[1:24] == "List of All Frequencies": # Start of the IR/Raman frequency section self.updateprogress(inputfile, "Frequency information", self.fupdate) # self.vibsyms = [] # Need to look into this a bit more self.vibirs = [] self.vibfreqs = [] for i in range(8): line = next(inputfile) line = next(inputfile).strip() while line: temp = line.split() self.vibfreqs.append(float(temp[0])) self.vibirs.append(float(temp[2])) # or is it temp[1]? line = next(inputfile).strip() self.vibfreqs = numpy.array(self.vibfreqs, "d") self.vibirs = numpy.array(self.vibirs, "d") if hasattr(self, "vibramans"): self.vibramans = numpy.array(self.vibramans, "d") #******************************************************************************************************************8 #delete this after new implementation using smat, eigvec print,eprint? # Extract the number of basis sets if line[1:49] == "Total nr. of (C)SFOs (summation over all irreps)": nbasis = int(line.split(":")[1].split()[0]) self.set_attribute('nbasis', nbasis) # now that we're here, let's extract aonames self.fonames = [] self.start_indeces = {} self.atombasis = [[] for frag in self.frags] # parse atombasis in the case of trivial SFOs self.skip_line(inputfile, 'blank') note = next(inputfile) symoffset = 0 self.skip_line(inputfile, 'blank') line = next(inputfile) if len(line) > 2: # fix for ADF2006.01 as it has another note self.skip_line(inputfile, 'blank') line = next(inputfile) self.skip_line(inputfile, 'blank') self.nosymreps = [] while len(self.fonames) < self.nbasis: symline = next(inputfile) sym = symline.split()[1] line = next(inputfile) num = int(line.split(':')[1].split()[0]) self.nosymreps.append(num) #read until line "--------..." is found while line.find('-----') < 0: line = next(inputfile) line = next(inputfile) # the start of the first SFO while len(self.fonames) < symoffset + num: info = line.split() #index0 index1 occ2 energy3/4 fragname5 coeff6 orbnum7 orbname8 fragname9 if not sym in list(self.start_indeces.keys()): #have we already set the start index for this symmetry? self.start_indeces[sym] = int(info[1]) orbname = info[8] orbital = info[7] + orbname.replace(":", "") fragname = info[5] frag = fragname + info[9] coeff = float(info[6]) # parse atombasis only in the case that all coefficients are 1 # and delete it otherwise if hasattr(self, 'atombasis'): if coeff == 1.: ibas = int(info[0]) - 1 ifrag = int(info[9]) - 1 iat = self.frags[ifrag][0] self.atombasis[iat].append(ibas) else: del self.atombasis line = next(inputfile) while line.strip() and not line[:7].strip(): # while it's the same SFO # i.e. while not completely blank, but blank at the start info = line[43:].split() if len(info) > 0: # len(info)==0 for the second line of dvb_ir.adfout frag += "+" + fragname + info[-1] coeff = float(info[-4]) if coeff < 0: orbital += '-' + info[-3] + info[-2].replace(":", "") else: orbital += '+' + info[-3] + info[-2].replace(":", "") line = next(inputfile) # At this point, we are either at the start of the next SFO or at # a blank line...the end self.fonames.append("%s_%s" % (frag, orbital)) symoffset += num # blankline blankline next(inputfile) next(inputfile) if line[1:32] == "S F O P O P U L A T I O N S ,": #Extract overlap matrix # self.fooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") symoffset = 0 for nosymrep in self.nosymreps: line = next(inputfile) while line.find('===') < 10: # look for the symmetry labels line = next(inputfile) self.skip_lines(inputfile, ['b', 'b']) text = next(inputfile) if text[13:20] != "Overlap": # verify this has overlap info break self.skip_lines(inputfile, ['b', 'col', 'row']) if not hasattr(self, "fooverlaps"): # make sure there is a matrix to store this self.fooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") base = 0 while base < nosymrep: # have we read all the columns? for i in range(nosymrep - base): self.updateprogress(inputfile, "Overlap", self.fupdate) line = next(inputfile) parts = line.split()[1:] for j in range(len(parts)): k = float(parts[j]) self.fooverlaps[base + symoffset + j, base + symoffset + i] = k self.fooverlaps[base + symoffset + i, base + symoffset + j] = k #blank, blank, column for i in range(3): next(inputfile) base += 4 symoffset += nosymrep base = 0 # The commented code below makes the atombasis attribute based on the BAS function in ADF, # but this is probably not so useful, since SFOs are used to build MOs in ADF. # if line[1:54] == "BAS: List of all Elementary Cartesian Basis Functions": # # self.atombasis = [] # # # There will be some text, followed by a line: # # (power of) X Y Z R Alpha on Atom # while not line[1:11] == "(power of)": # line = inputfile.next() # dashes = inputfile.next() # blank = inputfile.next() # line = inputfile.next() # # There will be two blank lines when there are no more atom types. # while line.strip() != "": # atoms = [int(i)-1 for i in line.split()[1:]] # for n in range(len(atoms)): # self.atombasis.append([]) # dashes = inputfile.next() # line = inputfile.next() # while line.strip() != "": # indices = [int(i)-1 for i in line.split()[5:]] # for i in range(len(indices)): # self.atombasis[atoms[i]].append(indices[i]) # line = inputfile.next() # line = inputfile.next() if line[48:67] == "SFO MO coefficients": self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d")] spin = 0 symoffset = 0 lastrow = 0 # Section ends with "1" at beggining of a line. while line[0] != "1": line = next(inputfile) # If spin is specified, then there will be two coefficient matrices. if line.strip() == "***** SPIN 1 *****": self.mocoeffs = [numpy.zeros((self.nbasis, self.nbasis), "d"), numpy.zeros((self.nbasis, self.nbasis), "d")] # Bump up the spin. if line.strip() == "***** SPIN 2 *****": spin = 1 symoffset = 0 lastrow = 0 # Next symmetry. if line.strip()[:4] == "=== ": sym = line.split()[1] if self.nosymflag: aolist = list(range(self.nbasis)) else: aolist = self.symlist[sym][spin] # Add to the symmetry offset of AO ordering. symoffset += lastrow # Blocks with coefficient always start with "MOs :". if line[1:6] == "MOs :": # Next line has the MO index contributed to. monumbers = [int(n) for n in line[6:].split()] self.skip_lines(inputfile, ['occup', 'label']) # The table can end with a blank line or "1". row = 0 line = next(inputfile) while not line.strip() in ["", "1"]: info = line.split() if int(info[0]) < self.start_indeces[sym]: #check to make sure we aren't parsing CFs line = next(inputfile) continue self.updateprogress(inputfile, "Coefficients", self.fupdate) row += 1 coeffs = [float(x) for x in info[1:]] moindices = [aolist[n-1] for n in monumbers] # The AO index is 1 less than the row. aoindex = symoffset + row - 1 for i in range(len(monumbers)): self.mocoeffs[spin][moindices[i], aoindex] = coeffs[i] line = next(inputfile) lastrow = row # ************************************************************************** # * * # * Final excitation energies from Davidson algorithm * # * * # ************************************************************************** # # Number of loops in Davidson routine = 20 # Number of matrix-vector multiplications = 24 # Type of excitations = SINGLET-SINGLET # # Symmetry B.u # # ... several blocks ... # # Normal termination of EXCITATION program part if line[4:53] == "Final excitation energies from Davidson algorithm": while line[1:9] != "Symmetry" and "Normal termination" not in line: line = next(inputfile) symm = self.normalisesym(line.split()[1]) # Excitation energies E in a.u. and eV, dE wrt prev. cycle, # oscillator strengths f in a.u. # # no. E/a.u. E/eV f dE/a.u. # ----------------------------------------------------- # 1 0.17084 4.6488 0.16526E-01 0.28E-08 # ... while line.split() != ['no.', 'E/a.u.', 'E/eV', 'f', 'dE/a.u.'] and "Normal termination" not in line: line = next(inputfile) self.skip_line(inputfile, 'dashes') etenergies = [] etoscs = [] etsyms = [] line = next(inputfile) while len(line) > 2: info = line.split() etenergies.append(utils.convertor(float(info[2]), "eV", "wavenumber")) etoscs.append(float(info[3])) etsyms.append(symm) line = next(inputfile) # There is another section before this, with transition dipole moments, # but this should just skip past it. while line[1:53] != "Major MO -> MO transitions for the above excitations": line = next(inputfile) # Note that here, and later, the number of blank lines can vary between # version of ADF (extra lines are seen in 2013.01 unit tests, for example). self.skip_line(inputfile, 'blank') excitation_occupied = next(inputfile) header = next(inputfile) while not header.strip(): header = next(inputfile) header2 = next(inputfile) x_y_z = next(inputfile) line = next(inputfile) while not line.strip(): line = next(inputfile) # Before we start handeling transitions, we need to create mosyms # with indices; only restricted calcs are possible in ADF. counts = {} syms = [] for mosym in self.mosyms[0]: if list(counts.keys()).count(mosym) == 0: counts[mosym] = 1 else: counts[mosym] += 1 syms.append(str(counts[mosym]) + mosym) etsecs = [] printed_warning = False for i in range(len(etenergies)): etsec = [] info = line.split() while len(info) > 0: match = re.search('[^0-9]', info[1]) index1 = int(info[1][:match.start(0)]) text = info[1][match.start(0):] symtext = text[0].upper() + text[1:] sym1 = str(index1) + self.normalisesym(symtext) match = re.search('[^0-9]', info[3]) index2 = int(info[3][:match.start(0)]) text = info[3][match.start(0):] symtext = text[0].upper() + text[1:] sym2 = str(index2) + self.normalisesym(symtext) try: index1 = syms.index(sym1) except ValueError: if not printed_warning: self.logger.warning("Etsecs are not accurate!") printed_warning = True try: index2 = syms.index(sym2) except ValueError: if not printed_warning: self.logger.warning("Etsecs are not accurate!") printed_warning = True etsec.append([(index1, 0), (index2, 0), float(info[4])]) line = next(inputfile) info = line.split() etsecs.append(etsec) # Again, the number of blank lines between transition can vary. line = next(inputfile) while not line.strip(): line = next(inputfile) if not hasattr(self, "etenergies"): self.etenergies = etenergies else: self.etenergies += etenergies if not hasattr(self, "etoscs"): self.etoscs = etoscs else: self.etoscs += etoscs if not hasattr(self, "etsyms"): self.etsyms = etsyms else: self.etsyms += etsyms if not hasattr(self, "etsecs"): self.etsecs = etsecs else: self.etsecs += etsecs if "M U L L I K E N P O P U L A T I O N S" in line: if not hasattr(self, "atomcharges"): self.atomcharges = {} while line[1:5] != "Atom": line = next(inputfile) self.skip_line(inputfile, 'dashes') mulliken = [] line = next(inputfile) while line.strip(): mulliken.append(float(line.split()[2])) line = next(inputfile) self.atomcharges["mulliken"] = mulliken # Dipole moment is always printed after a point calculation, # and the reference point for this is always the origin (0,0,0) # and not necessarily the center of mass, as explained on the # ADF user mailing list (see cclib/cclib#113 for details). # # ============= # Dipole Moment *** (Debye) *** # ============= # # Vector : 0.00000000 0.00000000 0.00000000 # Magnitude: 0.00000000 # if line.strip()[:13] == "Dipole Moment": self.skip_line(inputfile, 'equals') # There is not always a blank line here, for example when the dipole and quadrupole # moments are printed after the multipole derived atomic charges. Still, to the best # of my knowledge (KML) the values are still in Debye. line = next(inputfile) if not line.strip(): line = next(inputfile) assert line.split()[0] == "Vector" dipole = [float(d) for d in line.split()[-3:]] reference = [0.0, 0.0, 0.0] if not hasattr(self, 'moments'): self.moments = [reference, dipole] else: try: assert self.moments[1] == dipole except AssertionError: self.logger.warning('Overwriting previous multipole moments with new values') self.moments = [reference, dipole] # Molecular response properties. if line.strip()[1:-1].strip() == "RESPONSE program part": while line.strip() != "Normal termination of RESPONSE program part": if "THE DIPOLE-DIPOLE POLARIZABILITY TENSOR:" in line: if not hasattr(self, 'polarizabilities'): self.polarizabilities = [] polarizability = numpy.empty(shape=(3, 3)) self.skip_lines(inputfile, ['b', 'FREQUENCY', 'coordinates']) # Ordering of rows/columns is Y, Z, X. ordering = [1, 2, 0] indices = list(itertools.product(ordering, ordering)) for i in range(3): tokens = next(inputfile).split() for j in range(3): polarizability[indices[(i*3)+j]] = tokens[j] self.polarizabilities.append(polarizability) line = next(inputfile) if line[:24] == ' Buffered I/O statistics': self.metadata['success'] = True
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" ## This information is in the control file. # $rundimensions # dim(fock,dens)=1860 # natoms=20 # nshell=40 # nbf(CAO)=60 # nbf(AO)=60 # dim(trafo[SAO<-->AO/CAO])=60 # rhfshells=1 if line[3:10]=="natoms=": self.natom=int(line[10:]) if line[3:11] == "nbf(AO)=": nmo = int(line.split('=')[1]) self.set_attribute('nbasis', nmo) self.set_attribute('nmo', nmo) # Extract the version number and optionally the build number. searchstr = ": TURBOMOLE" index = line.find(searchstr) if index > -1: line = line[index + len(searchstr):] tokens = line.split() self.metadata["package_version"] = tokens[0][1:].replace("-", ".") # Don't add revision information to the main package version for now. if tokens[1] == "(": revision = tokens[2] ## Atomic coordinates in job.last: # +--------------------------------------------------+ # | Atomic coordinate, charge and isotop information | # +--------------------------------------------------+ # # # atomic coordinates atom shells charge pseudo isotop # -2.69176330 -0.00007129 -0.44712612 c 3 6.000 0 0 # -1.69851645 -0.00007332 2.06488947 c 3 6.000 0 0 # 0.92683848 -0.00007460 2.49592179 c 3 6.000 0 0 # 2.69176331 -0.00007127 0.44712612 c 3 6.000 0 0 # 1.69851645 -0.00007331 -2.06488947 c 3 6.000 0 0 #... # -7.04373606 0.00092244 2.74543891 h 1 1.000 0 0 # -9.36352819 0.00017229 0.07445322 h 1 1.000 0 0 # -0.92683849 -0.00007461 -2.49592179 c 3 6.000 0 0 # -1.65164853 -0.00009927 -4.45456858 h 1 1.000 0 0 if 'Atomic coordinate, charge and isotop information' in line: while 'atomic coordinates' not in line: line = next(inputfile) atomcoords = [] atomnos = [] line = next(inputfile) while len(line) > 2: atomnos.append(self.periodic_table.number[line.split()[3].upper()]) atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in line.split()[:3]]) line = next(inputfile) self.append_attribute('atomcoords', atomcoords) self.set_attribute('atomnos', atomnos) self.set_attribute('natom', len(atomcoords)) # Frequency values in aoforce.out # mode 7 8 9 10 11 12 # # frequency 53.33 88.32 146.85 171.70 251.75 289.44 # # symmetry a a a a a a # # IR YES YES YES YES YES YES # |dDIP/dQ| (a.u.) 0.0002 0.0000 0.0005 0.0004 0.0000 0.0000 # intensity (km/mol) 0.05 0.00 0.39 0.28 0.00 0.00 # intensity ( % ) 0.05 0.00 0.40 0.28 0.00 0.00 # # RAMAN YES YES YES YES YES YES # # 1 c x 0.00000 0.00001 0.00000 -0.01968 -0.04257 0.00001 # y -0.08246 -0.08792 0.02675 -0.00010 0.00000 0.17930 # z 0.00001 0.00003 0.00004 -0.10350 0.11992 -0.00003 if 'NORMAL MODES and VIBRATIONAL FREQUENCIES (cm**(-1))' in line: vibfreqs, vibsyms, vibirs, vibdisps = [], [], [], [] while '**** force : all done ****' not in line: if line.strip().startswith('frequency'): freqs = [float(i.replace('i', '-')) for i in line.split()[1:]] vibfreqs.extend(freqs) self.skip_line(inputfile, ['b']) line = next(inputfile) if line.strip().startswith('symmetry'): syms = line.split()[1:] vibsyms.extend(syms) self.skip_lines(inputfile, ['b', 'IR', 'dQIP']) line = next(inputfile) if line.strip().startswith('intensity (km/mol)'): irs = [self.float(f) for f in line.split()[2:]] vibirs.extend(irs) self.skip_lines(inputfile, ['intensity', 'b', 'raman', 'b']) line = next(inputfile) x, y, z = [], [], [] while line.split(): x.append([float(i) for i in line.split()[3:]]) line = next(inputfile) y.append([float(i) for i in line.split()[1:]]) line = next(inputfile) z.append([float(i) for i in line.split()[1:]]) line = next(inputfile) for j in range(len(x[0])): disps = [] for i in range(len(x)): disps.append([x[i][j], y[i][j], z[i][j]]) vibdisps.append(disps) line = next(inputfile) self.set_attribute('vibfreqs', vibfreqs) self.set_attribute('vibsyms', vibsyms) self.set_attribute('vibirs', vibirs) self.set_attribute('vibdisps', vibdisps) # In this section we are parsing mocoeffs and moenergies from # the files like: mos, alpha and beta. # $scfmo scfconv=6 format(4d20.14) # # SCF total energy is -382.3457535740 a.u. # # # 1 a eigenvalue=-.97461484059799D+01 nsaos=60 # 0.69876828353937D+000.32405121159405D-010.87670894913921D-03-.85232349313288D-07 # 0.19361534257922D-04-.23841194890166D-01-.81711001390807D-020.13626356942047D-02 # ... # ... # $end if (line.startswith('$scfmo') or line.startswith('$uhfmo')) and line.find('scfconv') > 0: if line.strip().startswith('$uhfmo_alpha'): self.unrestricted = True # Need to skip the first line to start with lines starting with '#'. line = next(inputfile) while line.strip().startswith('#') and not line.find('eigenvalue') > 0: line = next(inputfile) moenergies = [] mocoeffs = [] while not line.strip().startswith('$'): info = re.match(".*eigenvalue=(?P<moenergy>[0-9D\.+-]{20})\s+nsaos=(?P<count>\d+).*", line) eigenvalue = self.float(info.group('moenergy')) orbital_energy = utils.convertor(eigenvalue, 'hartree', 'eV') moenergies.append(orbital_energy) single_coeffs = [] nsaos = int(info.group('count')) while(len(single_coeffs) < nsaos): line = next(inputfile) single_coeffs.extend(Turbomole.split_molines(line)) mocoeffs.append(single_coeffs) line = next(inputfile) max_nsaos = max([len(i) for i in mocoeffs]) for i in mocoeffs: while len(i) < max_nsaos: i.append(numpy.nan) if not hasattr(self, 'mocoeffs'): self.mocoeffs = [] if not hasattr(self, 'moenergies'): self.moenergies = [] self.mocoeffs.append(mocoeffs) self.moenergies.append(moenergies) # Parsing the scfenergies, scfvalues and scftargets from job.last file. # scf convergence criterion : increment of total energy < .1000000D-05 # and increment of one-electron energy < .1000000D-02 # # ... # ... # current damping : 0.700 # ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL # 1 -382.34543727790 -1396.8009423 570.56292464 0.000D+00 0.556D-09 # Exc = -57.835278090846 N = 69.997494722 # max. resid. norm for Fia-block= 2.782D-05 for orbital 33a # ... # ... # current damping : 0.750 # ITERATION ENERGY 1e-ENERGY 2e-ENERGY NORM[dD(SAO)] TOL # 3 -382.34575357399 -1396.8009739 570.56263988 0.117D-03 0.319D-09 # Exc = -57.835593208072 N = 69.999813370 # max. resid. norm for Fia-block= 7.932D-06 for orbital 33a # max. resid. fock norm = 8.105D-06 for orbital 33a # # convergence criteria satisfied after 3 iterations # # # ------------------------------------------ # | total energy = -382.34575357399 | # ------------------------------------------ # : kinetic energy = 375.67398458525 : # : potential energy = -758.01973815924 : # : virial theorem = 1.98255043001 : # : wavefunction norm = 1.00000000000 : # .......................................... if 'scf convergence criterion' in line: total_energy_threshold = self.float(line.split()[-1]) one_electron_energy_threshold = self.float(next(inputfile).split()[-1]) scftargets = [total_energy_threshold, one_electron_energy_threshold] self.append_attribute('scftargets', scftargets) iter_energy = [] iter_one_elec_energy = [] while 'convergence criteria satisfied' not in line: if 'ITERATION ENERGY' in line: line = next(inputfile) info = line.split() iter_energy.append(self.float(info[1])) iter_one_elec_energy.append(self.float(info[2])) line = next(inputfile) assert len(iter_energy) == len(iter_one_elec_energy), \ 'Different number of values found for total energy and one electron energy.' scfvalues = [[x - y, a - b] for x, y, a, b in zip(iter_energy[1:], iter_energy[:-1], iter_one_elec_energy[1:], iter_one_elec_energy[:-1])] self.append_attribute('scfvalues', scfvalues) while 'total energy' not in line: line = next(inputfile) scfenergy = utils.convertor(self.float(line.split()[4]), 'hartree', 'eV') self.append_attribute('scfenergies', scfenergy) # ********************************************************************** # * * # * RHF energy : -74.9644564256 * # * MP2 correlation energy (doubles) : -0.0365225363 * # * * # * Final MP2 energy : -75.0009789619 * # ... # * Norm of MP1 T2 amplitudes : 0.0673494687 * # * * # ********************************************************************** # OR # ********************************************************************** # * * # * RHF energy : -74.9644564256 * # * correlation energy : -0.0507799360 * # * * # * Final CCSD energy : -75.0152363616 * # * * # * D1 diagnostic : 0.0132 * # * * # ********************************************************************** if 'C C S D F 1 2 P R O G R A M' in line: while 'ccsdf12 : all done' not in line: if 'Final MP2 energy' in line: mp2energy = [utils.convertor(self.float(line.split()[5]), 'hartree', 'eV')] self.append_attribute('mpenergies', mp2energy) if 'Final CCSD energy' in line: ccenergy = [utils.convertor(self.float(line.split()[5]), 'hartree', 'eV')] self.append_attribute('ccenergies', ccenergy) line = next(inputfile) # ***************************************************** # * * # * SCF-energy : -74.49827196840999 * # * MP2-energy : -0.19254365976227 * # * total : -74.69081562817226 * # * * # * (MP2-energy evaluated from T2 amplitudes) * # * * # ***************************************************** if 'm p g r a d - program' in line: while 'ccsdf12 : all done' not in line: if 'MP2-energy' in line: line = next(inputfile) if 'total' in line: mp2energy = [utils.convertor(self.float(line.split()[3]), 'hartree', 'eV')] self.append_attribute('mpenergies', mp2energy) line = next(inputfile)
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" if line[3:11]=="nbf(AO)=": nmo=int(line[11:]) self.nbasis=nmo self.nmo=nmo if line[3:9]=="nshell": temp=line.split('=') homos=int(temp[1]) if line[0:6] == "$basis": print("Found basis") self.basis_lib=[] line = inputfile.next() line = inputfile.next() while line[0] != '*' and line[0] != '$': temp=line.split() line = inputfile.next() while line[0]=="#": line = inputfile.next() self.basis_lib.append(AtomBasis(temp[0], temp[1], inputfile)) line = inputfile.next() if line == "$ecp\n": self.ecp_lib=[] line = inputfile.next() line = inputfile.next() while line[0] != '*' and line[0] != '$': fields=line.split() atname=fields[0] ecpname=fields[1] line = inputfile.next() line = inputfile.next() fields=line.split() ncore = int(fields[2]) while line[0] != '*': line = inputfile.next() self.ecp_lib.append([atname, ecpname, ncore]) if line[0:6] == "$coord": if line[0:11] == "$coordinate": # print "Breaking" return # print "Found coords" self.atomcoords = [] self.atomnos = [] atomcoords = [] atomnos = [] line = inputfile.next() if line[0:5] == "$user": # print "Breaking" return while line[0] != "$": temp = line.split() atsym=temp[3].capitalize() atomnos.append(self.table.number[atsym]) atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[0:3]]) line = inputfile.next() self.atomcoords.append(atomcoords) self.atomnos = numpy.array(atomnos, "i") if line[14:32] == "atomic coordinates": atomcoords = [] atomnos = [] line = inputfile.next() while len(line) > 2: temp = line.split() atsym = temp[3].capitalize() atomnos.append(self.table.number[atsym]) atomcoords.append([utils.convertor(float(x), "bohr", "Angstrom") for x in temp[0:3]]) line = inputfile.next() if not hasattr(self,"atomcoords"): self.atomcoords = [] self.atomcoords.append(atomcoords) self.atomnos = numpy.array(atomnos, "i") if line[0:6] == "$atoms": print("parsing atoms") line = inputfile.next() self.atomlist=[] while line[0]!="$": temp=line.split() at=temp[0] atnosstr=temp[1] while atnosstr[-1] == ",": line = inputfile.next() temp=line.split() atnosstr=atnosstr+temp[0] # print "Debug:", atnosstr atlist=self.atlist(atnosstr) line = inputfile.next() temp=line.split() # print "Debug basisname (temp):",temp basisname=temp[2] ecpname='' line = inputfile.next() while(line.find('jbas')!=-1 or line.find('ecp')!=-1 or line.find('jkbas')!=-1): if line.find('ecp')!=-1: temp=line.split() ecpname=temp[2] line = inputfile.next() self.atomlist.append( (at, basisname, ecpname, atlist)) # I have no idea what this does, so "comment" out if line[3:10]=="natoms=": # if 0: self.natom=int(line[10:]) basistable=[] for i in range(0, self.natom, 1): for j in range(0, len(self.atomlist), 1): for k in range(0, len(self.atomlist[j][3]), 1): if self.atomlist[j][3][k]==i: basistable.append((self.atomlist[j][0], self.atomlist[j][1], self.atomlist[j][2])) self.aonames=[] counter=1 for a, b, c in basistable: ncore=0 if len(c) > 0: for i in range(0, len(self.ecp_lib), 1): if self.ecp_lib[i][0]==a and \ self.ecp_lib[i][1]==c: ncore=self.ecp_lib[i][2] for i in range(0, len(self.basis_lib), 1): if self.basis_lib[i].atname==a and self.basis_lib[i].basis_name==b: pa=a.capitalize() basis=self.basis_lib[i] s_counter=1 p_counter=2 d_counter=3 f_counter=4 g_counter=5 # this is a really ugly piece of code to assign the right labels to # basis functions on atoms with an ecp if ncore == 2: s_counter=2 elif ncore == 10: s_counter=3 p_counter=3 elif ncore == 18: s_counter=4 p_counter=4 elif ncore == 28: s_counter=4 p_counter=4 d_counter=4 elif ncore == 36: s_counter=5 p_counter=5 d_counter=5 elif ncore == 46: s_counter=5 p_counter=5 d_counter=6 for j in range(0, len(basis.symmetries), 1): if basis.symmetries[j]=='s': self.aonames.append("%s%d_%d%s" % \ (pa, counter, s_counter, "S")) s_counter=s_counter+1 elif basis.symmetries[j]=='p': self.aonames.append("%s%d_%d%s" % \ (pa, counter, p_counter, "PX")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, p_counter, "PY")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, p_counter, "PZ")) p_counter=p_counter+1 elif basis.symmetries[j]=='d': self.aonames.append("%s%d_%d%s" % \ (pa, counter, d_counter, "D 0")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, d_counter, "D+1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, d_counter, "D-1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, d_counter, "D+2")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, d_counter, "D-2")) d_counter=d_counter+1 elif basis.symmetries[j]=='f': self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F 0")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F+1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F-1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F+2")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F-2")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F+3")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "F-3")) elif basis.symmetries[j]=='g': self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "G 0")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "G+1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, f_counter, "G-1")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G+2")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G-2")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G+3")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G-3")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G+4")) self.aonames.append("%s%d_%d%s" % \ (pa, counter, g_counter, "G-4")) break counter=counter+1 if line=="$closed shells\n": line = inputfile.next() temp = line.split() occs = int(temp[1][2:]) self.homos = numpy.array([occs-1], "i") if line == "$alpha shells\n": line = inputfile.next() temp = line.split() occ_a = int(temp[1][2:]) line = inputfile.next() # should be $beta shells line = inputfile.next() # the beta occs temp = line.split() occ_b = int(temp[1][2:]) self.homos = numpy.array([occ_a-1,occ_b-1], "i") if line[12:24]=="OVERLAP(CAO)": line = inputfile.next() line = inputfile.next() overlaparray=[] self.aooverlaps=numpy.zeros( (self.nbasis, self.nbasis), "d") while line != " ----------------------\n": temp=line.split() overlaparray.extend(map(float, temp)) line = inputfile.next() counter=0 for i in range(0, self.nbasis, 1): for j in range(0, i+1, 1): self.aooverlaps[i][j]=overlaparray[counter] self.aooverlaps[j][i]=overlaparray[counter] counter=counter+1 if ( line[0:6] == "$scfmo" or line[0:12] == "$uhfmo_alpha" ) and line.find("scf") > 0: temp = line.split() if temp[1][0:7] == "scfdump": # self.logger.warning("SCF not converged?") print("SCF not converged?!") if line[0:12] == "$uhfmo_alpha": # if unrestricted, create flag saying so unrestricted = 1 else: unrestricted = 0 self.moenergies=[] self.mocoeffs=[] for spin in range(unrestricted + 1): # make sure we cover all instances title = inputfile.next() while(title[0] == "#"): title = inputfile.next() # mocoeffs = numpy.zeros((self.nbasis, self.nbasis), "d") moenergies = [] moarray=[] if spin == 1 and title[0:11] == "$uhfmo_beta": title = inputfile.next() while title[0] == "#": title = inputfile.next() while(title[0] != '$'): temp=title.split() orb_symm=temp[1] try: energy = float(temp[2][11:].replace("D", "E")) except ValueError: print(spin, ": ", title) orb_en = utils.convertor(energy,"hartree","eV") moenergies.append(orb_en) single_mo = [] while(len(single_mo)<self.nbasis): self.updateprogress(inputfile, "Coefficients", self.cupdate) title = inputfile.next() lines_coeffs=self.split_molines(title) single_mo.extend(lines_coeffs) moarray.append(single_mo) title = inputfile.next() # for i in range(0, len(moarray), 1): # for j in range(0, self.nbasis, 1): # try: # mocoeffs[i][j]=moarray[i][j] # except IndexError: # print "Index Error in mocoeffs.", spin, i, j # break mocoeffs = numpy.array(moarray,"d") self.mocoeffs.append(mocoeffs) self.moenergies.append(moenergies) if line[26:49] == "a o f o r c e - program": self.vibirs = [] self.vibfreqs = [] self.vibsyms = [] self.vibdisps = [] # while line[3:31] != "**** force : all done ****": if line[12:26] == "ATOMIC WEIGHTS": #begin parsing atomic weights self.vibmasses=[] line=inputfile.next() # lines ======= line=inputfile.next() # notes line=inputfile.next() # start reading temp=line.split() while(len(temp) > 0): self.vibmasses.append(float(temp[2])) line=inputfile.next() temp=line.split() if line[5:14] == "frequency": if not hasattr(self,"vibfreqs"): self.vibfreqs = [] self.vibfreqs = [] self.vibsyms = [] self.vibdisps = [] self.vibirs = [] temp=line.replace("i","-").split() freqs = [self.float(f) for f in temp[1:]] self.vibfreqs.extend(freqs) line=inputfile.next() line=inputfile.next() syms=line.split() self.vibsyms.extend(syms[1:]) line=inputfile.next() line=inputfile.next() line=inputfile.next() line=inputfile.next() temp=line.split() irs = [self.float(f) for f in temp[2:]] self.vibirs.extend(irs) line=inputfile.next() line=inputfile.next() line=inputfile.next() line=inputfile.next() x=[] y=[] z=[] line=inputfile.next() while len(line) > 1: temp=line.split() x.append(map(float, temp[3:])) line=inputfile.next() temp=line.split() y.append(map(float, temp[1:])) line=inputfile.next() temp=line.split() z.append(map(float, temp[1:])) line=inputfile.next() # build xyz vectors for each mode for i in range(0, len(x[0]), 1): disp=[] for j in range(0, len(x), 1): disp.append( [x[j][i], y[j][i], z[j][i]]) self.vibdisps.append(disp)
def test_convertor(self): self.assertEqual("%.3f" % utils.convertor(8.0, "eV", "cm-1"), "64524.354")
def read_scan_logfile(logfiles, structure): """ parses Guassian09 torsion-scan log file parameters ---------- logfiles: str of list of str Name of Guassian 09 torsion scan log file structure: charmm psf file returns ------- TorsionScanSet """ topology = md.load_psf(structure) structure = CharmmPsfFile(structure) positions = np.ndarray((0, topology.n_atoms, 3)) qm_energies = np.ndarray(0) torsions = np.ndarray((0, 4), dtype=int) directions = np.ndarray(0, dtype=int) steps = np.ndarray((0, 3), dtype=int) if type(logfiles) != list: logfiles = [logfiles] for file in logfiles: print("loading %s" % file) direction = np.ndarray(1) torsion = np.ndarray((1, 4), dtype=int) step = [] index = (2, 12, -1) f = file.split("/")[-1].split(".") if f[2] == "pos": direction[0] = 1 else: direction[0] = 0 fi = open(file, "r") for line in fi: if re.search(" Scan ", line): t = line.split()[2].split(",") t[0] = t[0][-1] t[-1] = t[-1][0] for i in range(len(t)): torsion[0][i] = int(t[i]) - 1 if re.search("Step", line): try: step = np.array(([int(line.rsplit()[j]) for j in index])) step = step[np.newaxis, :] steps = np.append(steps, step, axis=0) except: pass fi.close() log = Gaussian(file) data = log.parse() # convert angstroms to nanometers positions = np.append(positions, data.atomcoords * 0.1, axis=0) qm_energies = np.append( qm_energies, (convertor(data.scfenergies, "eV", "kJmol-1") - min(convertor(data.scfenergies, "eV", "kJmol-1"))), axis=0, ) for i in range(len(data.scfenergies)): torsions = np.append(torsions, torsion, axis=0) directions = np.append(directions, direction, axis=0) return TorsionScanSet(positions, topology, structure, torsions, directions, steps, qm_energies)
def _parse_orbitals(self, inputfile, line): # From this block aonames, atombasis, moenergies and mocoeffs can be parsed. The data is # flipped compared to most programs (GAMESS, Gaussian), since the MOs are in rows. Also, Molpro # does not cut the table into parts, rather each MO row has as many lines as it takes ro print # all of the MO coefficients. Each row normally has 10 coefficients, although this can be less # for the last row and when symmetry is used (each irrep has its own block). # # ELECTRON ORBITALS # ================= # # # Orb Occ Energy Couls-En Coefficients # # 1 1s 1 1s 1 2px 1 2py 1 2pz 2 1s (...) # 3 1s 3 1s 3 2px 3 2py 3 2pz 4 1s (...) # (...) # # 1.1 2 -11.0351 -43.4915 0.701460 0.025696 -0.000365 -0.000006 0.000000 0.006922 (...) # -0.006450 0.004742 -0.001028 -0.002955 0.000000 -0.701460 (...) # (...) # # If an MCSCF calculation was performed, the natural orbitals # (coefficients and occupation numbers) are printed in a # format nearly identical to the ELECTRON ORBITALS section. # # NATURAL ORBITALS (state averaged) # ================================= # # Orb Occ Energy Coefficients # # 1 s 1 s 1 s 1 z 1 z 1 xx 1 yy 1 zz 2 s 2 s # 2 s 2 z 2 z 2 xx 2 yy 2 zz 3 s 3 s 3 z 3 y # # 1.1 2.00000 -20.678730 0.000141 -0.000057 0.001631 -0.001377 0.001117 0.000029 0.000293 -0.000852 1.000748 0.001746 # -0.002552 -0.002005 0.001658 -0.001266 -0.001274 -0.001001 0.000215 -0.000131 -0.000242 -0.000126 # # 2.1 2.00000 -11.322823 1.000682 0.004626 -0.000485 0.006634 -0.002096 -0.003072 -0.003282 -0.001724 -0.000181 0.006734 # -0.002398 -0.000527 0.001335 0.000091 0.000058 0.000396 -0.003219 0.000981 0.000250 -0.000191 # (...) # The assigment of final cclib attributes is different for # canonical/natural orbitals. self.naturalorbitals = (line[1:17] == "NATURAL ORBITALS") # Make sure we didn't get here by mistake. assert line[1:18] == "ELECTRON ORBITALS" or self.electronorbitals or self.naturalorbitals # For unrestricted calculations, ELECTRON ORBITALS is followed on the same line # by FOR POSITIVE SPIN or FOR NEGATIVE SPIN as appropriate. spin = (line[19:36] == "FOR NEGATIVE SPIN") or (self.electronorbitals[19:36] == "FOR NEGATIVE SPIN") if self.naturalorbitals: self.skip_lines(inputfile, ['equals', 'b', 'headers', 'b']) else: if not self.electronorbitals: self.skip_line(inputfile, 'equals') self.skip_lines(inputfile, ['b', 'b', 'headers', 'b']) aonames = [] atombasis = [[] for i in range(self.natom)] moenergies = [] # Use for both canonical and natural orbital coefficients. mocoeffs = [] occnos = [] line = next(inputfile) # Besides a double blank line, stop when the next orbitals are encountered for unrestricted jobs # or if there are stars on the line which always signifies the end of the block. while line.strip() and (not "ORBITALS" in line) and (not set(line.strip()) == {'*'}): # The function names are normally printed just once, but if symmetry is used then each irrep # has its own mocoeff block with a preceding list of names. is_aonames = line[:25].strip() == "" if is_aonames: # We need to save this offset for parsing the coefficients later. offset = len(aonames) aonum = len(aonames) while line.strip(): for s in line.split(): if s.isdigit(): atomno = int(s) atombasis[atomno-1].append(aonum) aonum += 1 else: functype = s element = self.table.element[self.atomnos[atomno-1]] aoname = "%s%i_%s" % (element, atomno, functype) aonames.append(aoname) line = next(inputfile) # Now there can be one or two blank lines. while not line.strip(): line = next(inputfile) # Newer versions of Molpro (for example, 2012 test files) will print some # more things here, such as H**O and LUMO, but these have less than 10 columns. if "H**O" in line or "LUMO" in line: break # End of the NATURAL ORBITALS section. if "Natural orbital dump" in line: break # Now parse the MO coefficients, padding the list with an appropriate amount of zeros. coeffs = [0.0 for i in range(offset)] while line.strip() != "": if line[:31].rstrip(): tokens = line.split() moenergy = float(tokens[2]) moenergy = utils.convertor(moenergy, "hartree", "eV") moenergies.append(moenergy) if self.naturalorbitals: occno = float(tokens[1]) occnos.append(occno) # Coefficients are in 10.6f format and splitting does not work since there are not # always spaces between them. If the numbers are very large, there will be stars. str_coeffs = line[31:] ncoeffs = len(str_coeffs) // 10 coeff = [] for ic in range(ncoeffs): p = str_coeffs[ic*10:(ic+1)*10] try: c = float(p) except ValueError as detail: self.logger.warn("setting coeff element to zero: %s" % detail) c = 0.0 coeff.append(c) coeffs.extend(coeff) line = next(inputfile) mocoeffs.append(coeffs) # The loop should keep going until there is a double blank line, and there is # a single line between each coefficient block. line = next(inputfile) if not line.strip(): line = next(inputfile) # If symmetry was used (offset was needed) then we will need to pad all MO vectors # up to nbasis for all irreps before the last one. if offset > 0: for im, m in enumerate(mocoeffs): if len(m) < self.nbasis: mocoeffs[im] = m + [0.0 for i in range(self.nbasis - len(m))] self.set_attribute('atombasis', atombasis) self.set_attribute('aonames', aonames) if self.naturalorbitals: # Consistent with current cclib conventions, keep only the # last possible set of natural orbital coefficients and # occupation numbers. self.nocoeffs = mocoeffs self.nooccnos = occnos else: # Consistent with current cclib conventions, reset moenergies/mocoeffs if they have been # previously parsed, since we want to produce only the final values. if not hasattr(self, "moenergies") or spin == 0: self.mocoeffs = [] self.moenergies = [] self.moenergies.append(moenergies) self.mocoeffs.append(mocoeffs) # Check if last line begins the next ELECTRON ORBITALS section, because we already used # this line and need to know when this method is called next time. if line[1:18] == "ELECTRON ORBITALS": self.electronorbitals = line else: self.electronorbitals = "" return
def extract(self, inputfile, line): """Extract information from the file object inputfile.""" # Extract the package version number. if "Jaguar version" in line: tokens = line.split() # Don't add revision information to the main package # version for now. # package_version = "{}.r{}".format(tokens[3][:-1], tokens[5]) package_version = tokens[3][:-1] self.metadata["package_version"] = package_version # Extract the basis set name if line[2:12] == "basis set:": self.metadata["basis_set"] = line.split()[2] # Extract charge and multiplicity if line[2:22] == "net molecular charge": self.set_attribute('charge', int(line.split()[-1])) self.set_attribute('mult', int(next(inputfile).split()[-1])) # The Gaussian basis set information is printed before the geometry, and we need # to do some indexing to get this into cclib format, because fn increments # for each engular momentum, but cclib does not (we have just P instead of # all three X/Y/Z with the same parameters. On the other hand, fn enumerates # the atomic orbitals correctly, so use it to build atombasis. # # Gaussian basis set information # # renorm mfac*renorm # atom fn prim L z coef coef coef # -------- ----- ---- --- ------------- ----------- ----------- ----------- # C1 1 1 S 7.161684E+01 1.5433E-01 2.7078E+00 2.7078E+00 # C1 1 2 S 1.304510E+01 5.3533E-01 2.6189E+00 2.6189E+00 # ... # C1 3 6 X 2.941249E+00 2.2135E-01 1.2153E+00 1.2153E+00 # 4 Y 1.2153E+00 # 5 Z 1.2153E+00 # C1 2 8 S 2.222899E-01 1.0000E+00 2.3073E-01 2.3073E-01 # C1 3 7 X 6.834831E-01 8.6271E-01 7.6421E-01 7.6421E-01 # ... # C2 6 1 S 7.161684E+01 1.5433E-01 2.7078E+00 2.7078E+00 # ... # if line.strip() == "Gaussian basis set information": self.skip_lines(inputfile, ['b', 'renorm', 'header', 'd']) # This is probably the only place we can get this information from Jaguar. self.gbasis = [] atombasis = [] line = next(inputfile) fn_per_atom = [] while line.strip(): if len(line.split()) > 3: aname = line.split()[0] fn = int(line.split()[1]) prim = int(line.split()[2]) L = line.split()[3] z = float(line.split()[4]) coef = float(line.split()[5]) # The primitive count is reset for each atom, so use that for adding # new elements to atombasis and gbasis. We could also probably do this # using the atom name, although that perhaps might not always be unique. if prim == 1: atombasis.append([]) fn_per_atom = [] self.gbasis.append([]) # Remember that fn is repeated when functions are contracted. if not fn-1 in atombasis[-1]: atombasis[-1].append(fn-1) # Here we use fn only to know when a new contraction is encountered, # so we don't need to decrement it, and we don't even use all values. # What's more, since we only wish to save the parameters for each subshell # once, we don't even need to consider lines for orbitals other than # those for X*, making things a bit easier. if not fn in fn_per_atom: fn_per_atom.append(fn) label = {'S': 'S', 'X': 'P', 'XX': 'D', 'XXX': 'F'}[L] self.gbasis[-1].append((label, [])) igbasis = fn_per_atom.index(fn) self.gbasis[-1][igbasis][1].append([z, coef]) else: fn = int(line.split()[0]) L = line.split()[1] # Some AO indices are only printed in these lines, for L > 0. if not fn-1 in atombasis[-1]: atombasis[-1].append(fn-1) line = next(inputfile) # The indices for atombasis can also be read later from the molecular orbital output. self.set_attribute('atombasis', atombasis) # This length of atombasis should always be the number of atoms. self.set_attribute('natom', len(self.atombasis)) # Effective Core Potential # # Atom Electrons represented by ECP # Mo 36 # Maximum angular term 3 # F Potential 1/r^n Exponent Coefficient # ----- -------- ----------- # 0 140.4577691 -0.0469492 # 1 89.4739342 -24.9754989 # ... # S-F Potential 1/r^n Exponent Coefficient # ----- -------- ----------- # 0 33.7771969 2.9278406 # 1 10.0120020 34.3483716 # ... # O 0 # Cl 10 # Maximum angular term 2 # D Potential 1/r^n Exponent Coefficient # ----- -------- ----------- # 1 94.8130000 -10.0000000 # ... if line.strip() == "Effective Core Potential": self.skip_line(inputfile, 'blank') line = next(inputfile) assert line.split()[0] == "Atom" assert " ".join(line.split()[1:]) == "Electrons represented by ECP" self.coreelectrons = [] line = next(inputfile) while line.strip(): if len(line.split()) == 2: self.coreelectrons.append(int(line.split()[1])) line = next(inputfile) if line[2:14] == "new geometry" or line[1:21] == "Symmetrized geometry" or line.find("Input geometry") > 0: # Get the atom coordinates if not hasattr(self, "atomcoords") or line[1:21] == "Symmetrized geometry": # Wipe the "Input geometry" if "Symmetrized geometry" present self.atomcoords = [] p = re.compile("(\D+)\d+") # One/more letters followed by a number atomcoords = [] atomnos = [] angstrom = next(inputfile) title = next(inputfile) line = next(inputfile) while line.strip(): temp = line.split() element = p.findall(temp[0])[0] atomnos.append(self.table.number[element]) atomcoords.append(list(map(float, temp[1:]))) line = next(inputfile) self.atomcoords.append(atomcoords) self.atomnos = numpy.array(atomnos, "i") self.set_attribute('natom', len(atomcoords)) # Hartree-Fock energy after SCF if line[1:18] == "SCFE: SCF energy:": self.metadata["methods"].append("HF") if not hasattr(self, "scfenergies"): self.scfenergies = [] temp = line.strip().split() scfenergy = float(temp[temp.index("hartrees") - 1]) scfenergy = utils.convertor(scfenergy, "hartree", "eV") self.scfenergies.append(scfenergy) # Energy after LMP2 correction if line[1:18] == "Total LMP2 Energy": self.metadata["methods"].append("LMP2") if not hasattr(self, "mpenergies"): self.mpenergies = [[]] lmp2energy = float(line.split()[-1]) lmp2energy = utils.convertor(lmp2energy, "hartree", "eV") self.mpenergies[-1].append(lmp2energy) if line[15:45] == "Geometry optimization complete": if not hasattr(self, 'optdone'): self.optdone = [] self.optdone.append(len(self.geovalues) - 1) if line.find("number of occupied orbitals") > 0: # Get number of MOs occs = int(line.split()[-1]) line = next(inputfile) virts = int(line.split()[-1]) self.nmo = occs + virts self.homos = numpy.array([occs-1], "i") self.unrestrictedflag = False if line[1:28] == "number of occupied orbitals": self.homos = numpy.array([float(line.strip().split()[-1])-1], "i") if line[2:27] == "number of basis functions": nbasis = int(line.strip().split()[-1]) self.set_attribute('nbasis', nbasis) if line.find("number of alpha occupied orb") > 0: # Get number of MOs for an unrestricted calc aoccs = int(line.split()[-1]) line = next(inputfile) avirts = int(line.split()[-1]) line = next(inputfile) boccs = int(line.split()[-1]) line = next(inputfile) bvirt = int(line.split()[-1]) self.nmo = aoccs + avirts self.homos = numpy.array([aoccs-1, boccs-1], "i") self.unrestrictedflag = True if line[0:4] == "etot": # Get SCF convergence information if not hasattr(self, "scfvalues"): self.scfvalues = [] self.scftargets = [[5E-5, 5E-6]] values = [] while line[0:4] == "etot": # Jaguar 4.2 # etot 1 N N 0 N -382.08751886450 2.3E-03 1.4E-01 # etot 2 Y Y 0 N -382.27486023153 1.9E-01 1.4E-03 5.7E-02 # Jaguar 6.5 # etot 1 N N 0 N -382.08751881733 2.3E-03 1.4E-01 # etot 2 Y Y 0 N -382.27486018708 1.9E-01 1.4E-03 5.7E-02 temp = line.split()[7:] if len(temp) == 3: denergy = float(temp[0]) else: denergy = 0 # Should really be greater than target value # or should we just ignore the values in this line ddensity = float(temp[-2]) maxdiiserr = float(temp[-1]) if not self.geoopt: values.append([denergy, ddensity]) else: values.append([ddensity]) try: line = next(inputfile) except StopIteration: self.logger.warning('File terminated before end of last SCF! Last error: {}'.format(maxdiiserr)) break self.scfvalues.append(values) # MO energies and symmetries. # Jaguar 7.0: provides energies and symmetries for both # restricted and unrestricted calculations, like this: # Alpha Orbital energies/symmetry label: # -10.25358 Bu -10.25353 Ag -10.21931 Bu -10.21927 Ag # -10.21792 Bu -10.21782 Ag -10.21773 Bu -10.21772 Ag # ... # Jaguar 6.5: prints both only for restricted calculations, # so for unrestricted calculations the output it looks like this: # Alpha Orbital energies: # -10.25358 -10.25353 -10.21931 -10.21927 -10.21792 -10.21782 # -10.21773 -10.21772 -10.21537 -10.21537 -1.02078 -0.96193 # ... # Presence of 'Orbital energies' is enough to catch all versions. if "Orbital energies" in line: # Parsing results is identical for restricted/unrestricted # calculations, just assert later that alpha/beta order is OK. spin = int(line[2:6] == "Beta") # Check if symmetries are printed also. issyms = "symmetry label" in line if not hasattr(self, "moenergies"): self.moenergies = [] if issyms and not hasattr(self, "mosyms"): self.mosyms = [] # Grow moeneriges/mosyms and make sure they are empty when # parsed multiple times - currently cclib returns only # the final output (ex. in a geomtry optimization). if len(self.moenergies) < spin+1: self.moenergies.append([]) self.moenergies[spin] = [] if issyms: if len(self.mosyms) < spin+1: self.mosyms.append([]) self.mosyms[spin] = [] line = next(inputfile).split() while len(line) > 0: if issyms: energies = [float(line[2*i]) for i in range(len(line)//2)] syms = [line[2*i+1] for i in range(len(line)//2)] else: energies = [float(e) for e in line] energies = [utils.convertor(e, "hartree", "eV") for e in energies] self.moenergies[spin].extend(energies) if issyms: syms = [self.normalisesym(s) for s in syms] self.mosyms[spin].extend(syms) line = next(inputfile).split() line = next(inputfile) # The second trigger string is in the version 8.3 unit test and the first one was # encountered in version 6.x and is followed by a bit different format. In particular, # the line with occupations is missing in each block. Here is a fragment of this block # from version 8.3: # # ***************************************** # # occupied + virtual orbitals: final wave function # # ***************************************** # # # 1 2 3 4 5 # eigenvalues- -11.04064 -11.04058 -11.03196 -11.03196 -11.02881 # occupations- 2.00000 2.00000 2.00000 2.00000 2.00000 # 1 C1 S 0.70148 0.70154 -0.00958 -0.00991 0.00401 # 2 C1 S 0.02527 0.02518 0.00380 0.00374 0.00371 # ... # if line.find("Occupied + virtual Orbitals- final wvfn") > 0 or \ line.find("occupied + virtual orbitals: final wave function") > 0: self.skip_lines(inputfile, ['b', 's', 'b', 'b']) if not hasattr(self, "mocoeffs"): self.mocoeffs = [] aonames = [] lastatom = "X" readatombasis = False if not hasattr(self, "atombasis"): self.atombasis = [] for i in range(self.natom): self.atombasis.append([]) readatombasis = True offset = 0 spin = 1 + int(self.unrestrictedflag) for s in range(spin): mocoeffs = numpy.zeros((len(self.moenergies[s]), self.nbasis), "d") if s == 1: # beta case self.skip_lines(inputfile, ['s', 'b', 'title', 'b', 's', 'b', 'b']) for k in range(0, len(self.moenergies[s]), 5): self.updateprogress(inputfile, "Coefficients") # All known version have a line with indices followed by the eigenvalues. self.skip_lines(inputfile, ['numbers', 'eigens']) # Newer version also have a line with occupation numbers here. line = next(inputfile) if "occupations-" in line: line = next(inputfile) for i in range(self.nbasis): info = line.split() # Fill atombasis only first time around. if readatombasis and k == 0: orbno = int(info[0]) atom = info[1] if atom[1].isalpha(): atomno = int(atom[2:]) else: atomno = int(atom[1:]) self.atombasis[atomno-1].append(orbno-1) if not hasattr(self, "aonames"): if lastatom != info[1]: scount = 1 pcount = 3 dcount = 6 # six d orbitals in Jaguar if info[2] == 'S': aonames.append("%s_%i%s" % (info[1], scount, info[2])) scount += 1 if info[2] == 'X' or info[2] == 'Y' or info[2] == 'Z': aonames.append("%s_%iP%s" % (info[1], pcount / 3, info[2])) pcount += 1 if info[2] == 'XX' or info[2] == 'YY' or info[2] == 'ZZ' or \ info[2] == 'XY' or info[2] == 'XZ' or info[2] == 'YZ': aonames.append("%s_%iD%s" % (info[1], dcount / 6, info[2])) dcount += 1 lastatom = info[1] for j in range(len(info[3:])): mocoeffs[j+k, i] = float(info[3+j]) line = next(inputfile) if not hasattr(self, "aonames"): self.aonames = aonames offset += 5 self.mocoeffs.append(mocoeffs) # Atomic charges from Mulliken population analysis: # # Atom C1 C2 C3 C4 C5 # Charge 0.00177 -0.06075 -0.05956 0.00177 -0.06075 # # Atom H6 H7 H8 C9 C10 # ... if line.strip() == "Atomic charges from Mulliken population analysis:": if not hasattr(self, 'atomcharges'): self.atomcharges = {} charges = [] self.skip_line(inputfile, "blank") line = next(inputfile) while "sum of atomic charges" not in line: assert line.split()[0] == "Atom" line = next(inputfile) assert line.split()[0] == "Charge" charges.extend([float(c) for c in line.split()[1:]]) self.skip_line(inputfile, "blank") line = next(inputfile) self.atomcharges['mulliken'] = charges if (line[2:6] == "olap") or (line.strip() == "overlap matrix:"): if line[6] == "-": return # This was continue (in loop) before parser refactoring. # continue # avoid "olap-dev" self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") for i in range(0, self.nbasis, 5): self.updateprogress(inputfile, "Overlap") self.skip_lines(inputfile, ['b', 'header']) for j in range(i, self.nbasis): temp = list(map(float, next(inputfile).split()[1:])) self.aooverlaps[j, i:(i+len(temp))] = temp self.aooverlaps[i:(i+len(temp)), j] = temp if line[2:24] == "start of program geopt": if not self.geoopt: # Need to keep only the RMS density change info # if this is a geooptz self.scftargets = [[self.scftargets[0][0]]] if hasattr(self, "scfvalues"): self.scfvalues[0] = [[x[0]] for x in self.scfvalues[0]] self.geoopt = True else: self.scftargets.append([5E-5]) # Get Geometry Opt convergence information # # geometry optimization step 7 # energy: -382.30219111487 hartrees # [ turning on trust-radius adjustment ] # ** restarting optimization from step 6 ** # # # Level shifts adjusted to satisfy step-size constraints # Step size: 0.0360704 # Cos(theta): 0.8789215 # Final level shift: -8.6176299E-02 # # energy change: 2.5819E-04 . ( 5.0000E-05 ) # gradient maximum: 5.0947E-03 . ( 4.5000E-04 ) # gradient rms: 1.2996E-03 . ( 3.0000E-04 ) # displacement maximum: 1.3954E-02 . ( 1.8000E-03 ) # displacement rms: 4.6567E-03 . ( 1.2000E-03 ) # if line[2:28] == "geometry optimization step": if not hasattr(self, "geovalues"): self.geovalues = [] self.geotargets = numpy.zeros(5, "d") gopt_step = int(line.split()[-1]) energy = next(inputfile) blank = next(inputfile) # A quick hack for messages that show up right after the energy # at this point, which include: # ** restarting optimization from step 2 ** # [ turning on trust-radius adjustment ] # as found in regression file ptnh3_2_H2O_2_2plus.out and other logfiles. restarting_from_1 = False while blank.strip(): if blank.strip() == "** restarting optimization from step 1 **": restarting_from_1 = True blank = next(inputfile) # One or more blank lines, depending on content. line = next(inputfile) while not line.strip(): line = next(inputfile) # Note that the level shift message is followed by a blank, too. if "Level shifts adjusted" in line: while line.strip(): line = next(inputfile) line = next(inputfile) # The first optimization step does not produce an energy change, and # ther is also no energy change when the optimization is restarted # from step 1 (since step 1 had no change). values = [] target_index = 0 if (gopt_step == 1) or restarting_from_1: values.append(0.0) target_index = 1 while line.strip(): if len(line) > 40 and line[41] == "(": # A new geo convergence value values.append(float(line[26:37])) self.geotargets[target_index] = float(line[43:54]) target_index += 1 line = next(inputfile) self.geovalues.append(values) # IR output looks like this: # frequencies 72.45 113.25 176.88 183.76 267.60 312.06 # symmetries Au Bg Au Bu Ag Bg # intensities 0.07 0.00 0.28 0.52 0.00 0.00 # reduc. mass 1.90 0.74 1.06 1.42 1.19 0.85 # force const 0.01 0.01 0.02 0.03 0.05 0.05 # C1 X 0.00000 0.00000 0.00000 -0.05707 -0.06716 0.00000 # C1 Y 0.00000 0.00000 0.00000 0.00909 -0.02529 0.00000 # C1 Z 0.04792 -0.06032 -0.01192 0.00000 0.00000 0.11613 # C2 X 0.00000 0.00000 0.00000 -0.06094 -0.04635 0.00000 # ... etc. ... # This is a complete ouput, some files will not have intensities, # and older Jaguar versions sometimes skip the symmetries. if line[2:23] == "start of program freq": self.skip_line(inputfile, 'blank') # Version 8.3 has two blank lines here, earlier versions just one. line = next(inputfile) if not line.strip(): line = next(inputfile) self.vibfreqs = [] self.vibdisps = [] forceconstants = False intensities = False while line.strip(): if "force const" in line: forceconstants = True if "intensities" in line: intensities = True line = next(inputfile) # In older version, the last block had an extra blank line after it, # which could be caught. This is not true in newer version (including 8.3), # but in general it would be better to bound this loop more strictly. freqs = next(inputfile) while freqs.strip() and not "imaginary frequencies" in freqs: # Number of modes (columns printed in this block). nmodes = len(freqs.split())-1 # Append the frequencies. self.vibfreqs.extend(list(map(float, freqs.split()[1:]))) line = next(inputfile).split() # May skip symmetries (older Jaguar versions). if line[0] == "symmetries": if not hasattr(self, "vibsyms"): self.vibsyms = [] self.vibsyms.extend(list(map(self.normalisesym, line[1:]))) line = next(inputfile).split() if intensities: if not hasattr(self, "vibirs"): self.vibirs = [] self.vibirs.extend(list(map(float, line[1:]))) line = next(inputfile).split() if forceconstants: line = next(inputfile) # Start parsing the displacements. # Variable 'q' holds up to 7 lists of triplets. q = [[] for i in range(7)] for n in range(self.natom): # Variable 'p' holds up to 7 triplets. p = [[] for i in range(7)] for i in range(3): line = next(inputfile) disps = [float(disp) for disp in line.split()[2:]] for j in range(nmodes): p[j].append(disps[j]) for i in range(nmodes): q[i].append(p[i]) self.vibdisps.extend(q[:nmodes]) self.skip_line(inputfile, 'blank') freqs = next(inputfile) # Convert new data to arrays. self.vibfreqs = numpy.array(self.vibfreqs, "d") self.vibdisps = numpy.array(self.vibdisps, "d") if hasattr(self, "vibirs"): self.vibirs = numpy.array(self.vibirs, "d") # Parse excited state output (for CIS calculations). # Jaguar calculates only singlet states. if line[2:15] == "Excited State": if not hasattr(self, "etenergies"): self.etenergies = [] if not hasattr(self, "etoscs"): self.etoscs = [] if not hasattr(self, "etsecs"): self.etsecs = [] self.etsyms = [] etenergy = float(line.split()[3]) etenergy = utils.convertor(etenergy, "eV", "wavenumber") self.etenergies.append(etenergy) self.skip_lines(inputfile, ['line', 'line', 'line', 'line']) line = next(inputfile) self.etsecs.append([]) # Jaguar calculates only singlet states. self.etsyms.append('Singlet-A') while line.strip() != "": fromMO = int(line.split()[0])-1 toMO = int(line.split()[2])-1 coeff = float(line.split()[-1]) self.etsecs[-1].append([(fromMO, 0), (toMO, 0), coeff]) line = next(inputfile) # Skip 3 lines for i in range(4): line = next(inputfile) strength = float(line.split()[-1]) self.etoscs.append(strength) if line[:20] == ' Total elapsed time:' \ or line[:18] == ' Total cpu seconds': self.metadata['success'] = True
for qmoutputfile in args.qmoutputfile: qmoutputfile = os.path.abspath(qmoutputfile) print(qmoutputfile) job = ccopen(qmoutputfile) if job: data = job.parse() idx_homo = data.homos[-1] idx_lumo = idx_homo + 1 steps = len(data.scfenergies) total_e = convertor(data.scfenergies[-1], 'eV', 'hartree') h**o = convertor(data.moenergies[-1][idx_homo], 'eV', 'hartree') lumo = convertor(data.moenergies[-1][idx_lumo], 'eV', 'hartree') job_type = determine_job_type(job) total_time = extract_total_time(qmoutputfile, job_type) time_per_step = total_time / steps else: data = parse_non_cclib_output(qmoutputfile) print('Time (min): {}'.format(total_time)) print('Steps : {}'.format(steps)) print('per step : {}'.format(time_per_step)) print('Total E : {}'.format(total_e)) print('H**O : {}'.format(h**o)) print('LUMO : {}'.format(lumo))