def test_write_and_read_trivial_trajectories(self): """Ensure write and read trajectory files with single structures.""" # An open-shell single point calculation. data = ccopen("data/ORCA/basicORCA4.2/dvb_sp_un.out").parse() cclib2ase.write_trajectory("dvb_sp_un.traj", data) trajdata = cclib2ase.read_trajectory("dvb_sp_un.traj") assert np.allclose(trajdata.atomcoords, data.atomcoords) assert np.allclose(trajdata.scfenergies, data.scfenergies) # No grads here. assert np.allclose(trajdata.atomnos, data.atomnos) assert np.allclose(trajdata.atommasses, data.atommasses) assert np.allclose(trajdata.natom, data.natom) assert np.allclose(trajdata.charge, data.charge) assert np.allclose(trajdata.mult, data.mult) assert np.allclose(trajdata.moments, data.moments) # No temperature here. # No freeenergy here. assert np.allclose(trajdata.atomcharges["mulliken"], data.atomcharges["mulliken"]) assert np.allclose(trajdata.atomspins["mulliken"], data.atomspins["mulliken"]) # A closed-shell single structure frequency calculation. data = ccopen("data/ORCA/basicORCA4.2/dvb_ir.out").parse() cclib2ase.write_trajectory("dvb_ir.traj", data) trajdata = cclib2ase.read_trajectory("dvb_ir.traj") assert np.allclose(trajdata.atomcoords, data.atomcoords) assert np.allclose(trajdata.scfenergies, data.scfenergies) # No grads here. assert np.allclose(trajdata.atomnos, data.atomnos) assert np.allclose(trajdata.atommasses, data.atommasses) assert np.allclose(trajdata.natom, data.natom) assert np.allclose(trajdata.charge, data.charge, atol=1e-5) assert np.allclose(trajdata.mult, data.mult) assert np.allclose(trajdata.moments, data.moments) # No temperature here. # No freeenergy here. assert np.allclose(trajdata.atomcharges["mulliken"], data.atomcharges["mulliken"])
def test_gaussian_tinker(directory): if directory.endswith('UFF'): pytest.skip() with temporary_directory() as tmp: # Original data data_original = os.path.join(data, directory) outfile_original = os.path.join(data_original, directory + '.out') infile_original = os.path.join(data_original, directory + '.in') # Copied tmp paths data_copy = os.path.join(tmp, directory) infile_copy = os.path.join(data_copy, directory + '.in') shutil.copytree(data_original, data_copy) os.chdir(data_copy) # We are now in /tmp/garleek*****/A_1 or similar, which # contains copies of the original Gaussian files and types # guess forcefield specified in atom.types ff = 'qmmm3.prm' types = 'uff_to_mm3' if os.path.isfile(directory + '.key'): ff = directory + '.key' elif os.path.isfile('atom.types'): types = 'atom.types' with open(types) as f: for line in f: if line.startswith('# forcefield:'): ff = line.split(':', 1)[1].strip() # patch inputfile garleek_in = frontend_garleek(infile_copy, qm='gaussian_'+gaussian_version, mm='tinker', ff=ff, types=types) call([gaussian_exe, garleek_in]) garleek_out = os.path.splitext(garleek_in)[0] + '.log' # Save output in working dir if not os.path.exists(os.path.join(WORKING_DIR, 'outputs-'+gaussian_version)): os.mkdir(os.path.join(WORKING_DIR, 'outputs-'+gaussian_version)) shutil.copy(garleek_in, os.path.join(WORKING_DIR, 'outputs-'+gaussian_version, os.path.basename(garleek_in))) assert os.path.isfile(garleek_out) shutil.copy(garleek_out, os.path.join(WORKING_DIR, 'outputs-'+gaussian_version, os.path.basename(garleek_out))) assert no_errors(garleek_out) # Check values assert isclose(oniom_energy(outfile_original), oniom_energy(garleek_out)) if HAS_CCLIB: cc_original = cclib.ccopen(outfile_original).parse() cc_calculated = cclib.ccopen(garleek_out).parse() assert isclose(cc_original.scfenergies[-1], cc_calculated.scfenergies[-1]) assert np.sqrt(np.mean(np.square(cc_original.atomcoords[-1]-cc_calculated.atomcoords[-1]))) < 0.001
def logfile_to_dict(logfile, verbose=False): # reading with cclib data = cclib.ccopen(logfile).parse() data_json = json.loads(data.writejson()) # openbabel sur XYZ obdata = pybel.readstring("xyz", data.writexyz()) # construct new dict return full_report(logfile, data_json, data, obdata, verbose=verbose)
def main(argv=sys.argv[1:]): """ Pnictogen command-line interface. It writes inputs for sets of molecules. Parameters ---------- argv : list of str Arguments as taken from the command-line Examples -------- Although this function is not intended to be called from Python code (see the pnictogen function below for one that is), the following should work: >>> import pnictogen >>> pnictogen.main(["-g", "/tmp/opt.ORCA.inp"]) /tmp/opt.ORCA.inp written >>> pnictogen.main(["/tmp/opt.ORCA.inp", "data/co.xyz", ... "data/water.xyz"]) data/co.inp written data/water.inp written This is exactly as if pnictogen were called from the command-line. """ parser = argparser() args = parser.parse_args(argv) package, extension = os.path.basename(args.template).split(".")[-2:] if args.generate: with open(REPOSITORY[package], "r") as stream: content = stream.read() with open(args.template, "w") as stream: stream.write(content) print("{:s} written".format(args.template)) else: for descriptor in args.descriptors: input_prefix, description_extension = os.path.splitext(descriptor) try: molecule = Atoms(cclib.ccopen(descriptor).parse()) except KeyError: molecule = Atoms( cclib.bridge.cclib2openbabel.readfile( descriptor, description_extension[1:])) if not molecule.name: molecule.name = descriptor written_files = pnictogen(molecule, input_prefix, args.template, extension) for written_file in written_files: print("{:s} written".format(written_file))
def quality_check_lvl1(logfile, verbose=False): if verbose: print(">>> START QC lvl1 <<<") try: # reading with cclib data = cclib.ccopen(logfile).parse() if verbose: print("OK\n>>> END QC lvl1 <<<\n") except: raise ScanlogException("Quality check lvl1 failed : LOG file not readable (cclib failed on file %s)." % logfile) solver = data.metadata['package'] return solver
def test_makease_works_with_openshells(self): """Ensure makease works from parsed data for open-shell molecules.""" # make sure we can construct an open shell molecule data = ccopen("data/ORCA/basicORCA4.2/dvb_sp_un.out").parse() # Check we have no gradients, as they will be generated by ASE. with self.assertRaises(AttributeError): data.grads dvb_sp_un = cclib2ase.makease( data.atomcoords[-1], data.atomnos, data.atomcharges["mulliken"], data.atomspins["mulliken"], data.atommasses, ) # check whether converting back gives the expected data ase_data = cclib2ase.makecclib(dvb_sp_un) assert np.allclose(ase_data.atomcoords, [data.atomcoords[-1]]) assert np.allclose(ase_data.atomnos, data.atomnos) assert np.allclose(ase_data.atomcharges["mulliken"], data.atomcharges["mulliken"]) assert np.allclose(ase_data.atomspins["mulliken"], data.atomspins["mulliken"]) assert np.allclose(ase_data.atommasses, data.atommasses) assert np.isclose(ase_data.charge, data.charge) assert np.isclose(ase_data.mult, data.mult) assert np.isclose(ase_data.natom, len(data.atomnos)) assert np.isclose(ase_data.temperature, 0) # make sure our object is compatible with ASE API dvb_sp_un.calc = EMT(label="dvb_sp_un") # not a serious calculator! # converting back should give updated results ase_data = cclib2ase.makecclib(dvb_sp_un) assert np.allclose(ase_data.atomcoords, [data.atomcoords[-1]]) assert np.allclose(ase_data.atomnos, data.atomnos) assert np.allclose(ase_data.atommasses, data.atommasses) assert np.allclose(ase_data.atomcharges["mulliken"], data.atomcharges["mulliken"]) assert np.allclose( ase_data.atomspins["mulliken"], 0) # spin densities not supported but overwritten by EMT assert np.isclose(ase_data.charge, data.charge) assert np.isclose(ase_data.mult, 1) assert np.isclose(ase_data.natom, len(data.atomnos)) assert np.isclose(ase_data.temperature, 0) # Both energies and gradients are from the EMT calculation. assert np.allclose(ase_data.scfenergies, [7.016800805424298]) assert np.shape(ase_data.grads) == (1, ase_data.natom, 3)
def extract(self, log_file_name: str) -> dict: """ Extract all possible features from a quantum chemistry calculation log file. """ parsed = cclib.ccopen(log_file_name).parse() if not self._is_success(parsed): raise RuntimeError('Calculation in {} is not successful.'.format( log_file_name )) result = {} for method in self._all_methods(): method(parsed, result) return result
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 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 test_write_and_read_opt_trajectories(self): """Ensure write and read trajectory files with optimizations.""" # Geometry optimization. data = ccopen("data/ORCA/basicORCA4.2/dvb_gopt.out").parse() cclib2ase.write_trajectory("dvb_gopt.traj", data) trajdata = cclib2ase.read_trajectory("dvb_gopt.traj") assert np.allclose(trajdata.atomcoords, data.atomcoords) assert np.allclose(trajdata.scfenergies, data.scfenergies) assert np.allclose(trajdata.grads, data.grads) assert np.allclose(trajdata.atomnos, data.atomnos) assert np.allclose(trajdata.atommasses, data.atommasses) assert np.allclose(trajdata.natom, data.natom) assert np.allclose(trajdata.charge, data.charge) assert np.allclose(trajdata.mult, data.mult) assert np.allclose(trajdata.moments, data.moments, atol=1e-5) # No temperature here. # No freeenergy here. assert np.allclose(trajdata.atomcharges["mulliken"], data.atomcharges["mulliken"])
def test_makease_works_with_closedshells(self): """Ensure makease works from parsed data for closed-shell molecules.""" # Make sure we can construct a closed shell molecule. data = ccopen("data/ORCA/basicORCA4.2/dvb_ir.out").parse() dvb_ir = cclib2ase.makease( data.atomcoords[-1], data.atomnos, data.atomcharges["mulliken"], None, # no atomspins data.atommasses, ) # check whether converting back gives the expected data. ase_data = cclib2ase.makecclib(dvb_ir) assert np.allclose(ase_data.atomcoords, [data.atomcoords[-1]]) assert np.allclose(ase_data.atomnos, data.atomnos) assert np.allclose(ase_data.atomcharges["mulliken"], data.atomcharges["mulliken"]) assert np.allclose(ase_data.atomspins["mulliken"], 0) assert np.allclose(ase_data.atommasses, data.atommasses) assert np.isclose(ase_data.charge, data.charge, atol=1e-5) assert np.isclose(ase_data.mult, data.mult) assert np.isclose(ase_data.natom, len(data.atomnos)) assert np.isclose(ase_data.temperature, 0)
def main(): """Run main procedure.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("logfile") # TODO(schneiderfelipe): set charge and multiplicity parser.add_argument( "-a", "--acc", help="accuracy for SCC calculation, lower is better", type=float, default=1.0, ) parser.add_argument( "--iterations", help="number of iterations in SCC", type=int, default=250, ) parser.add_argument( "--gfn", help="specify parametrisation of GFN-xTB", type=int ) parser.add_argument( "--etemp", help="electronic temperature", type=float, default=300.0 ) parser.add_argument( "-s", "--solvent", help=("solvent (SMD/GBSA implicit solvation models)"), default="none", ) parser.add_argument( "--do-not-cache-api", dest="cache_api", help="Do not reuse generate API objects (not recommended)", action="store_false", ) parser.add_argument( "--pm3", help="use PM3", action="store_true", ) parser.add_argument( "--b97-3c", help="use B97-3c", action="store_true", ) parser.add_argument( "--minimize", action="store_true", ) parser.add_argument( "--transition-state", action="store_true", ) parser.add_argument("--max-omega", type=float, default=1.0) parser.add_argument("--tol", type=float, default=1e-3) parser.add_argument("--nprocs", type=int, default=4) args = parser.parse_args() print(args) data = ccopen(args.logfile).parse() initial_positions = data.atomcoords[-1] atoms = Atoms(numbers=data.atomnos, positions=initial_positions) if args.gfn: method = f"GFN{args.gfn}-xTB" solvent = smd2gbsa[args.solvent.lower()] calc = XTB( method=method, accuracy=args.acc, electronic_temperature=args.etemp, max_iterations=args.iterations, solvent=solvent, cache_api=args.cache_api, ) else: if args.b97_3c: method = "B97-3c D3BJ def2-SV(P)" elif args.pm3: method = "PM3" else: def allow_keyword(keyword): for forbidden in {"freq", "opt", "irc", "print"}: if forbidden in keyword.lower(): return False return True keywords = [ keyword for keyword in data.metadata["keywords"] if allow_keyword(keyword) ] method = " ".join(keywords) solvent = args.solvent blocks = f"%pal\n nprocs {args.nprocs}\nend\n%scf\n maxiter {args.iterations}\nend" if solvent != "none" and not args.pm3: blocks += f'\n%cpcm\n smd true\n smdsolvent "{solvent}"\nend' if "ORCA_COMMAND" not in os.environ: # For parallel runs ORCA has to be called with full pathname os.environ["ORCA_COMMAND"] = shutil.which("orca") calc = ORCA( label="012345_swing", orcasimpleinput=method, orcablocks=blocks ) print(f"*** {method} ***") print(f" : solvent: {solvent}") atoms.set_calculator(calc) potential_min = atoms.get_potential_energy() print(f"@ potential energy: {potential_min} eV") indices = np.where(data.vibfreqs < 0)[0] n_indices = len(indices) print(f"@ imaginary frequencies: {data.vibfreqs[indices]}") if not n_indices: print(" : nothing to be done, bye") return ignoring = None if args.transition_state: ignoring = 0 print(" : transition state: ignoring first imaginary frequency") omegas = [] potentials = [] def f(omega): atoms.set_positions( initial_positions + np.einsum("i,ijk->jk", omega, data.vibdisps[indices]) ) potential = 1e3 * (atoms.get_potential_energy() - potential_min) omegas.append(omega) potentials.append(potential) print(f" : omega: {omega}") print(f" : potential: {potential} meV") return potential if args.minimize: guesses = [np.zeros_like(indices, dtype=float)] for i in indices: if ignoring is not None and i == ignoring: continue print(f"@ searching in direction #{i}") def g(w): z = np.zeros_like(indices, dtype=float) z[i] = w return f(z) if args.minimize: res = minimize_scalar( g, method="bounded", bounds=(-args.max_omega, args.max_omega), tol=args.tol, ) print(res) guess = np.zeros_like(indices, dtype=float) guess[i] = res.x guesses.append(guess) else: dx = args.max_omega / 100 x = [-dx, 0.0, dx] y = [g(-dx), 0.0, g(dx)] # p[0] * x**2 + p[1] * x + p[2] == k * (x - x0)**2 == k * x**2 - 2 * x0 * k * x + k * x0**2 p = np.polyfit(x, y, 2) print(p) print(np.roots(p)) dp = np.polyder(p) print(dp) r = np.roots(dp) print(r) # k = p[0] # x0 = np.sqrt(p[2] / k) # print(k, x0) # print(root(lambda z: [p[0] - z[0], p[1] + 2 * z[0] * z[1], p[2] - z[0] * z[1] ** 2], [k, x0])) best_positions = initial_positions + np.einsum( "i,ijk->jk", r, data.vibdisps[indices] ) if args.minimize: print("@ choosing initial guess for global search") if n_indices > 1: guesses.append(np.sum(guesses, axis=0)) x0 = guesses[np.argmin([f(guess) for guess in guesses])] print("@ searching in all directions") constraints = () if args.transition_state and ignoring is not None: constraints = ( {"type": "eq", "fun": lambda omega: omega[ignoring]}, ) res = minimize( f, x0=x0, bounds=n_indices * [(-args.max_omega, args.max_omega)], constraints=constraints, tol=args.tol, ) print(res) best_positions = initial_positions + np.einsum( "i,ijk->jk", res.x, data.vibdisps[indices] ) # TODO(schneiderfelipe): correct for when using --transition-state omegas = np.array(omegas) fig, ax = plt.subplots(n_indices, 1) if n_indices == 1: ax = [ax] xlim = (-args.max_omega - 0.05, args.max_omega + 0.05) ylim = (np.min(potentials) - 2.0, 40.0) for i in indices: if ignoring is not None and i == ignoring: continue ax[i].plot(omegas[:, i], potentials, "o") ax[i].set_title(f"view of normal mode #{i}") ax[i].set_ylabel(r"potential energy (meV)") ax[i].set_xlabel(rf"$\omega_{i}$") ax[i].set_ylim(ylim) ax[i].set_xlim(xlim) plt.tight_layout() plt.show() print("@ writing best geometry to swinged.xyz") # TODO(schneiderfelipe): print a RMSD between initial and final structures atoms.set_positions(best_positions) atoms.write("swinged.xyz", format="xyz", plain=True)
def main(): parser = argparse.ArgumentParser() parser.add_argument("logfiles", metavar="logfile", nargs="+") parser.add_argument("--xmin", default=400.0, type=float) parser.add_argument("--xmax", default=700.0, type=float) parser.add_argument("--xshift", default=90.0, type=float) parser.add_argument("--broad-only", action="store_true") args = parser.parse_args() for logfile_path in args.logfiles: # We ask for label=path pairs. parts = logfile_path.split("=") name = parts[0] logfile_path = parts[-1] print(name.center(80, "-")) print(logfile_path) spectrum_path = logfile_path.replace(".out", ".spectrum") spectrum_path_found = os.path.isfile(spectrum_path) if not args.broad_only and spectrum_path_found: print(".spectrum file found") spectrum = pd.read_csv(spectrum_path, sep="\s+", index_col=0) x = spectrum.index y = spectrum["TotalSpectrum"] / spectrum["TotalSpectrum"].max() else: if spectrum_path_found: print("Ignoring found .spectrum file, using broadened data") else: print("No .spectrum file found, using broadened data") data = ccopen(logfile_path).parse() wavelengths = 1e7 / data.etenergies # nm conversion x = np.linspace(wavelengths.min() - 100.0, wavelengths.max() + 100.0, num=1000) y = broaden_spectrum(x, wavelengths, data.etoscs, scale=40.0) y = y / y.max() if args.xshift: print(f"Shifting all wavelengths by {args.xshift} nm") x += args.xshift plt.plot(x, y, label=name) f = interp1d(x, y, kind="cubic", bounds_error=False, fill_value="extrapolate") res = minimize_scalar( lambda t: -f(t), bracket=(args.xmin, args.xmax), bounds=(args.xmin, args.xmax), method="bounded", ) print(res) plt.xlim(args.xmin, args.xmax) plt.xlabel("wavelength (nm)") plt.ylabel("arbitrary units") plt.legend() plt.show()