def normal_termination(self): clean_directory() self.log( f'\n--> TSCoDe normal termination: total time {time_to_string(time.perf_counter() - self.t_start_run, verbose=True)}.' ) self.logfile.close() quit()
def run_tests(): import os import time from subprocess import CalledProcessError os.chdir(os.path.dirname(os.path.realpath(__file__))) from tscode.settings import (CALCULATOR, COMMANDS, DEFAULT_FF_LEVELS, DEFAULT_LEVELS, FF_CALC, FF_OPT_BOOL, PROCS) if CALCULATOR not in ('MOPAC','ORCA','GAUSSIAN','XTB'): raise Exception(f'{CALCULATOR} is not a valid calculator. Use MOPAC, ORCA, GAUSSIAN or XTB.') import numpy as np from ase.atoms import Atoms from ase.optimize import LBFGS from tscode.ase_manipulations import get_ase_calc from tscode.optimization_methods import opt_funcs_dict from tscode.utils import (HiddenPrints, clean_directory, loadbar, read_xyz, run_command, time_to_string) os.chdir('tests') t_start_run = time.perf_counter() data = read_xyz('C2H4.xyz') ########################################################################## print('\nRunning tests for TSCoDe. Settings used:') print(f'{CALCULATOR=}') if CALCULATOR != 'XTB': print(f'{CALCULATOR} COMMAND = {COMMANDS[CALCULATOR]}') print('\nTesting calculator...') ########################################################################## opt_funcs_dict[CALCULATOR](data.atomcoords[0], data.atomnos, method=DEFAULT_LEVELS[CALCULATOR], procs=PROCS, read_output=False) print(f'{CALCULATOR} raw calculator works.') ########################################################################## atoms = Atoms('HH', positions=np.array([[0, 0, 0], [0, 0, 1]])) atoms.calc = get_ase_calc((CALCULATOR, DEFAULT_LEVELS[CALCULATOR], PROCS, None)) LBFGS(atoms, logfile=None).run() clean_directory() print(f'{CALCULATOR} ASE calculator works.') ########################################################################## print(f'\n{FF_OPT_BOOL=}') ff = f'on. Calculator is {FF_CALC}. Checking its status.' if FF_OPT_BOOL else 'off.' print(f'Force Field optimization is turned {ff}') if FF_OPT_BOOL: if FF_CALC == 'OB': try: print('Trying to import the OpenBabel Python Module...') from openbabel import openbabel print('Module imported successfully.') except ImportError: raise Exception(f'Could not import OpenBabel Python module. Is standalone openbabel correctly installed?') else: # == 'XTB', 'GAUSSIAN' opt_funcs_dict[FF_CALC](data.atomcoords[0], data.atomnos, method=DEFAULT_FF_LEVELS[FF_CALC], procs=PROCS, read_output=False) print(f'{FF_CALC} FF raw calculator works.') ########################################################################## atoms.calc = get_ase_calc((FF_CALC, DEFAULT_FF_LEVELS[FF_CALC], PROCS, None)) LBFGS(atoms, logfile=None).run() clean_directory() print(f'{FF_CALC} ASE calculator works.') print('\nNo installation faults detected with the current settings. Running tests.') ########################################################################## tests = [] for f in os.listdir(): if f.endswith('.txt'): tests.append(os.path.realpath(f)) # os.chdir(os.path.dirname(os.getcwd())) # os.chdir('tscode') # # Back to ./tscode times = [] for i, f in enumerate(tests): name = f.split('\\')[-1].split('/')[-1][:-4] # trying to make it work for either Win, Linux (and Mac?) loadbar(i, len(tests), f'Running TSCoDe tests ({name}): ') t_start = time.perf_counter() try: with HiddenPrints(): run_command(f'python -m tscode {f} -n {name}') except CalledProcessError as error: print('\n\n--> An error occurred:\n') print(error.stderr.decode("utf-8")) quit() t_end = time.perf_counter() times.append(t_end-t_start) loadbar(len(tests), len(tests), f'Running TSCoDe tests ({name}): ') print() for i, f in enumerate(tests): print(' {:25s}{} s'.format(f.split('\\')[-1].split('/')[-1][:-4], round(times[i], 3))) print(f'\nTSCoDe tests completed with no errors. ({time_to_string(time.perf_counter() - t_start_run)})\n')
def ase_bend(embedder, original_mol, conf, pivot, threshold, title='temp', traj=None, check=True): ''' embedder: TSCoDe embedder object original_mol: Hypermolecule object to be bent conf: index of conformation in original_mol to be used pivot: pivot connecting two Hypermolecule orbitals to be approached/distanced threshold: target distance for the specified pivot, in Angstroms title: name to be used for referring to this structure in the embedder log traj: if set to a string, traj+'.traj' is used as a filename for the bending trajectory. not only the atoms will be printed, but also all the orbitals and the active pivot. check: if True, after bending checks that the bent structure did not scramble. If it did, returns the initial molecule. ''' identifier = np.sum(original_mol.atomcoords[conf]) if hasattr(embedder, "ase_bent_mols_dict"): cached = embedder.ase_bent_mols_dict.get( (identifier, tuple(sorted(pivot.index)), round(threshold, 3))) if cached is not None: return cached if traj is not None: from ase.io.trajectory import Trajectory def orbitalized(atoms, orbitals, pivot=None): positions = np.concatenate((atoms.positions, orbitals)) if pivot is not None: positions = np.concatenate( (positions, [pivot.start], [pivot.end])) symbols = list(atoms.numbers) + [0 for _ in orbitals] if pivot is not None: symbols += [9 for _ in range(2)] # Fluorine (9) represents active orbitals new_atoms = Atoms(symbols, positions=positions) return new_atoms try: os.remove(traj) except FileNotFoundError: pass i1, i2 = original_mol.reactive_indexes neighbors_of_1 = neighbors(original_mol.graph, i1) neighbors_of_2 = neighbors(original_mol.graph, i2) mol = deepcopy(original_mol) final_mol = deepcopy(original_mol) for p in mol.pivots[conf]: if p.index == pivot.index: active_pivot = p break dist = norm_of(active_pivot.pivot) atoms = Atoms(mol.atomnos, positions=mol.atomcoords[conf]) atoms.calc = get_ase_calc(embedder) if traj is not None: traj_obj = Trajectory( traj + f'_conf{conf}.traj', mode='a', atoms=orbitalized( atoms, np.vstack([ atom.center for atom in mol.reactive_atoms_classes_dict[0].values() ]), active_pivot)) traj_obj.write() unproductive_iterations = 0 break_reason = 'MAX ITER' t_start = time.perf_counter() for iteration in range(500): atoms.positions = mol.atomcoords[0] orb_memo = { index: norm_of(atom.center[0] - atom.coord) for index, atom in mol.reactive_atoms_classes_dict[0].items() } orb1, orb2 = active_pivot.start, active_pivot.end c1 = OrbitalSpring(i1, i2, orb1, orb2, neighbors_of_1, neighbors_of_2, d_eq=threshold) c2 = PreventScramblingConstraint( mol.graph, atoms, double_bond_protection=embedder.options.double_bond_protection, fix_angles=embedder.options.fix_angles_in_deformation) atoms.set_constraint([ c1, c2, ]) opt = BFGS(atoms, maxstep=0.2, logfile=None, trajectory=None) try: opt.run(fmax=0.5, steps=1) except ValueError: # Shake did not converge break_reason = 'CRASHED' break if traj is not None: traj_obj.atoms = orbitalized( atoms, np.vstack([ atom.center for atom in mol.reactive_atoms_classes_dict[0].values() ])) traj_obj.write() # check if we are stuck if np.max( np.abs( np.linalg.norm(atoms.get_positions() - mol.atomcoords[0], axis=1))) < 0.01: unproductive_iterations += 1 if unproductive_iterations == 10: break_reason = 'STUCK' break else: unproductive_iterations = 0 mol.atomcoords[0] = atoms.get_positions() # Update orbitals and get temp pivots for index, atom in mol.reactive_atoms_classes_dict[0].items(): atom.init(mol, index, update=True, orb_dim=orb_memo[index]) # orbitals positions are calculated based on the conformer we are working on temp_pivots = embedder._get_pivots(mol)[0] for p in temp_pivots: if p.index == pivot.index: active_pivot = p break # print(active_pivot) dist = norm_of(active_pivot.pivot) # print(f'{iteration}. {mol.name} conf {conf}: pivot is {round(dist, 3)} (target {round(threshold, 3)})') if dist - threshold < 0.1: break_reason = 'CONVERGED' break # else: # print('delta is ', round(dist - threshold, 3)) embedder.log( f' {title} - conformer {conf} - {break_reason}{" "*(9-len(break_reason))} ({iteration+1}{" "*(3-len(str(iteration+1)))} iterations, {time_to_string(time.perf_counter()-t_start)})', p=False) if check: if not molecule_check(original_mol.atomcoords[conf], mol.atomcoords[0], mol.atomnos, max_newbonds=1): mol.atomcoords[0] = original_mol.atomcoords[conf] # keep the bent structures only if no scrambling occurred between atoms final_mol.atomcoords[conf] = mol.atomcoords[0] # Now align the ensembles on the new reactive atoms positions reference, *targets = final_mol.atomcoords reference = np.array(reference) targets = np.array(targets) r = reference - np.mean(reference[final_mol.reactive_indexes], axis=0) ts = np.array( [t - np.mean(t[final_mol.reactive_indexes], axis=0) for t in targets]) output = [] output.append(r) for target in ts: matrix = kabsch(r, target) output.append([matrix @ vector for vector in target]) final_mol.atomcoords = np.array(output) # Update orbitals and pivots for conf_, _ in enumerate(final_mol.atomcoords): for index, atom in final_mol.reactive_atoms_classes_dict[conf_].items( ): atom.init(final_mol, index, update=True, orb_dim=orb_memo[index]) embedder._set_pivots(final_mol) # add result to cache (if we have it) so we avoid recomputing it if hasattr(embedder, "ase_bent_mols_dict"): embedder.ase_bent_mols_dict[(identifier, tuple(sorted(pivot.index)), round(threshold, 3))] = final_mol clean_directory() return final_mol
def xtb_opt(coords, atomnos, constrained_indexes=None, method='GFN2-xTB', solvent=None, title='temp', read_output=True, **kwargs): ''' This function writes an XTB .inp file, runs it with the subprocess module and reads its output. :params coords: array of shape (n,3) with cartesian coordinates for atoms. :params atomnos: array of atomic numbers for atoms. :params constrained_indexes: array of shape (n,2), with the indexes of atomic pairs to be constrained. :params method: string, specifiyng the theory level to be used. :params title: string, used as a file name and job title for the mopac input file. :params read_output: Whether to read the output file and return anything. ''' with open(f'{title}.xyz', 'w') as f: write_xyz(coords, atomnos, f, title=title) s = f'$opt\n logfile={title}_opt.log\n$end' if constrained_indexes is not None: s += '\n$constrain\n' for a, b in constrained_indexes: s += ' distance: %s, %s, %s\n' % ( a + 1, b + 1, round(norm_of(coords[a] - coords[b]), 5)) if method.upper() in ('GFN-XTB', 'GFNXTB'): s += '\n$gfn\n method=1\n' elif method.upper() in ('GFN2-XTB', 'GFN2XTB'): s += '\n$gfn\n method=2\n' s += '\n$end' s = ''.join(s) with open(f'{title}.inp', 'w') as f: f.write(s) flags = '--opt' if method in ('GFN-FF', 'GFNFF'): flags += ' tight' # tighter convergence for GFN-FF works better flags += ' --gfnff' # declaring the use of FF instead of semiempirical if solvent is not None: if solvent == 'methanol': flags += f' --gbsa methanol' else: flags += f' --alpb {solvent}' elif method.upper() in ('GFN-FF', 'GFNFF'): flags += f' --alpb thf' try: check_call( f'xtb --input {title}.inp {title}.xyz {flags} > temp.log 2>&1'. split(), stdout=DEVNULL, stderr=STDOUT) except KeyboardInterrupt: print('KeyboardInterrupt requested by user. Quitting.') quit() if read_output: try: outname = 'xtbopt.xyz' opt_coords = read_xyz(outname).atomcoords[0] energy = read_xtb_energy(outname) clean_directory() os.remove(outname) for filename in ('gfnff_topo', 'charges', 'wbo', 'xtbrestart', 'xtbtopo.mol', '.xtboptok'): try: os.remove(filename) except FileNotFoundError: pass return opt_coords, energy, True except FileNotFoundError: return None, None, False
def ase_torsion_TSs(embedder, coords, atomnos, indexes, threshold_kcal=5, title='temp', optimization=True, logfile=None, bernytraj=None, plot=False): ''' ''' assert len(indexes) == 4 # cyclical = False ts_structures, energies = [], [] graph = graphize(coords, atomnos) i1, i2, i3, i4 = indexes if all([len(shortest_path(graph, start, end)) == 2 for start, end in zip(indexes[0:-1], indexes[1:])]): graph.remove_edge(i2, i3) subgraphs = connected_components(graph) for subgraph in subgraphs: if i3 in subgraph: indexes_to_be_moved = subgraph - {i3} break if i1 in indexes_to_be_moved: # cyclical = True indexes_to_be_moved = [i4] # if molecule is cyclical, just move the fourth atom and # let the rest of the structure relax s = 'The specified dihedral angle is comprised within a cycle. Switching to safe dihedral scan (moving only last index).' print(s) if logfile is not None: logfile.write(s+'\n') else: if not embedder.options.let: raise SystemExit('The specified dihedral angle is made up of non-contiguous atoms. To prevent errors, the\n' + 'run has been stopped. Override this behavior with the LET keyword.') # if user did not provide four contiguous indexes, # and did that on purpose, just move the fourth atom and # let the rest of the structure relax indexes_to_be_moved = [i4] # cyclical = True s = 'The specified dihedral angle is made up of non-contiguous atoms.\nThis might cause some unexpected results.' print(s) if logfile is not None: logfile.write(s+'\n') # routine = ((10, 18, '_clockwise'), (-10, 18, '_counterclockwise')) if cyclical else ((10, 36, ''),) routine = ((10, 36, '_clockwise'), (-10, 36, '_counterclockwise')) for degrees, steps, direction in routine: print() if logfile is not None: logfile.write('\n') structures, energies = ase_scan(embedder, coords, atomnos, indexes=indexes, degrees=degrees, steps=steps, relaxed=optimization, indexes_to_be_moved=indexes_to_be_moved, title='Preliminary scan' + ((' (clockwise)' if direction == '_clockwise' \ else ' (counterclockwise)') if direction != '' else ''), logfile=logfile) min_e = min(energies) rel_energies = [e-min_e for e in energies] tag = '_relaxed' if optimization else '_rigid' with open(title + tag + direction + '_scan.xyz', 'w') as outfile: for s, structure in enumerate(align_structures(np.array(structures), indexes[:-1])): write_xyz(structure, atomnos, outfile, title=f'Scan point {s+1}/{len(structures)} - Rel. E = {round(rel_energies[s], 3)} kcal/mol') if plot: import pickle import matplotlib.pyplot as plt fig = plt.figure() x1 = [dihedral(structure[indexes]) for structure in structures] y1 = [e-min_e for e in energies] for i, (x_, y_) in enumerate(get_plot_segments(x1, y1, max_step=abs(degrees)+1)): plt.plot(x_, y_, '-', color='tab:blue', label=('Preliminary SCAN'+direction) if i == 0 else None, linewidth=3, alpha=0.50) peaks_indexes = atropisomer_peaks(energies, min_thr=min_e+threshold_kcal, max_thr=min_e+75) if peaks_indexes: s = 's' if len(peaks_indexes) > 1 else '' print(f'Found {len(peaks_indexes)} peak{s}. Performing accurate scan{s}.\n') if logfile is not None: logfile.write(f'Found {len(peaks_indexes)} peak{s}. Performing accurate scan{s}.\n\n') for p, peak in enumerate(peaks_indexes): sub_structures, sub_energies = ase_scan(embedder, structures[peak-1], atomnos, indexes=indexes, degrees=degrees/10, #1° or -1° steps=20, relaxed=optimization, ad_libitum=True, # goes on until the hill is crossed indexes_to_be_moved=indexes_to_be_moved, title=f'Accurate scan {p+1}/{len(peaks_indexes)}', logfile=logfile) if logfile is not None: logfile.write('\n') if plot: x2 = [dihedral(structure[indexes]) for structure in sub_structures] y2 = [e-min_e for e in sub_energies] for i, (x_, y_) in enumerate(get_plot_segments(x2, y2, max_step=abs(degrees/10)+1)): plt.plot(x_, y_, '-o', color='tab:red', label='Accurate SCAN' if (p == 0 and i == 0) else None, markersize=1, linewidth=2, alpha=0.5) sub_peaks_indexes = atropisomer_peaks(sub_energies, min_thr=threshold_kcal+min_e, max_thr=min_e+75) if sub_peaks_indexes: s = 's' if len(sub_peaks_indexes) > 1 else '' msg = f'Found {len(sub_peaks_indexes)} sub-peak{s}.' if embedder.options.saddle or embedder.options.neb: if embedder.options.saddle: tag = 'saddle' else: tag = 'NEB TS' msg += f'Performing {tag} optimization{s}.' print(msg) if logfile is not None: logfile.write(s+'\n') for s, sub_peak in enumerate(sub_peaks_indexes): if plot: x = dihedral(sub_structures[sub_peak][indexes]) y = sub_energies[sub_peak]-min_e plt.plot(x, y, color='gold', marker='o', label='Maxima' if p == 0 else None, markersize=3) if embedder.options.saddle: loadbar_title = f' > Saddle opt on sub-peak {s+1}/{len(sub_peaks_indexes)}' # loadbar(s+1, len(sub_peaks_indexes), loadbar_title+' '*(29-len(loadbar_title))) print(loadbar_title) optimized_geom, energy, _ = ase_saddle(embedder, sub_structures[sub_peak], atomnos, title=f'Saddle opt - peak {p+1}, sub-peak {s+1}', logfile=logfile, traj=bernytraj+f'_{p+1}_{s+1}.traj' if bernytraj is not None else None) if molecule_check(coords, optimized_geom, atomnos): ts_structures.append(optimized_geom) energies.append(energy) elif embedder.options.neb: loadbar_title = f' > NEB TS opt on sub-peak {s+1}/{len(sub_peaks_indexes)}, {direction[1:]}' drctn = 'clkws' if direction == '_clockwise' else 'ccws' print(loadbar_title) optimized_geom, energy, success = ase_neb(embedder, sub_structures[sub_peak-2], sub_structures[(sub_peak+1)%len(sub_structures)], atomnos, n_images=5, title=f'{title}_NEB_peak_{p+1}_sub-peak_{s+1}_{drctn}', logfunction=embedder.log) if success and molecule_check(coords, optimized_geom, atomnos): ts_structures.append(optimized_geom) energies.append(energy) else: ts_structures.append(sub_structures[sub_peak]) energies.append(sub_energies[sub_peak]) print() else: print('No suitable sub-peaks found.\n') if logfile is not None: logfile.write('No suitable sub-peaks found.\n\n') else: print('No suitable peaks found.\n') if logfile is not None: logfile.write('No suitable peaks found.\n\n') if plot: plt.legend() plt.xlabel(f'Dihedral Angle {tuple(indexes)}') plt.ylabel('Energy (kcal/mol)') pickle.dump(fig, open(f'{title}{direction}_plt.pickle', 'wb')) plt.savefig(f'{title}{direction}_plt.svg') ts_structures = np.array(ts_structures) clean_directory() return ts_structures, energies
def ase_scan(embedder, coords, atomnos, indexes, degrees=10, steps=36, relaxed=True, ad_libitum=False, indexes_to_be_moved=None, title='temp scan', logfile=None): ''' if ad libitum, steps is the minimum number of performed steps ''' assert len(indexes) == 4 if ad_libitum: if not relaxed: raise Exception(f'The ad_libitum keyword is only available for relaxed scans.') atoms = Atoms(atomnos, positions=coords) structures, energies = [], [] atoms.calc = get_ase_calc(embedder) if indexes_to_be_moved is None: indexes_to_be_moved = range(len(atomnos)) mask = np.array([i in indexes_to_be_moved for i, _ in enumerate(atomnos)], dtype=bool) t_start = time() if logfile is not None: logfile.write(f' > {title}\n') for scan_step in range(1000): loadbar_title = f'{title} - step {scan_step+1}' if ad_libitum: print(loadbar_title, end='\r') else: loadbar_title += '/'+str(steps) loadbar(scan_step+1, steps, loadbar_title+' '*(29-len(loadbar_title))) if logfile is not None: t_start_step = time() if relaxed: atoms.set_constraint(FixInternals(dihedrals_deg=[[atoms.get_dihedral(*indexes), indexes]])) with LBFGS(atoms, maxstep=0.2, logfile=None, trajectory=None) as opt: try: opt.run(fmax=0.05, steps=500) exit_str = 'converged' except ValueError: # Shake did not converge exit_str = 'crashed' iterations = opt.nsteps energies.append(atoms.get_total_energy() * 23.06054194532933) # eV to kcal/mol if logfile is not None: elapsed = time() - t_start_step s = '/' + str(steps) if not ad_libitum else '' logfile.write(f' Step {scan_step+1}{s} - {exit_str} - {iterations} iterations ({time_to_string(elapsed)})\n') structures.append(atoms.get_positions()) atoms.rotate_dihedral(*indexes, angle=degrees, mask=mask) if exit_str == 'crashed': break elif scan_step+1 >= steps: if ad_libitum: if any(( (max(energies) - energies[-1]) > 1, (max(energies) - energies[-1]) > max(energies)-energies[0], (energies[-1] - min(energies)) > 50 )): # ad_libitum stops when one of these conditions is met: # - we surpassed and are below the maximum of at least 1 kcal/mol # - we surpassed maximum and are below starting point # - current step energy is more than 50 kcal/mol above starting point print(loadbar_title) break else: break structures = np.array(structures) clean_directory() if logfile is not None: elapsed = time() - t_start logfile.write(f'{title} - completed ({time_to_string(elapsed)})\n') return align_structures(structures, indexes), energies
def orca_opt(coords, atomnos, constrained_indexes=None, method='PM3', procs=1, solvent=None, title='temp', read_output=True): ''' This function writes an ORCA .inp file, runs it with the subprocess module and reads its output. :params coords: array of shape (n,3) with cartesian coordinates for atoms. :params atomnos: array of atomic numbers for atoms. :params constrained_indexes: array of shape (n,2), with the indexes of atomic pairs to be constrained. :params method: string, specifiyng the first line of keywords for the MOPAC input file. :params title: string, used as a file name and job title for the mopac input file. :params read_output: Whether to read the output file and return anything. ''' s = '! %s Opt\n\n# ORCA input generated by TSCoDe\n\n' % (method) if solvent is not None: s += '\n' + get_solvent_line(solvent, 'ORCA', method) + '\n' if procs > 1: s += f'%pal nprocs {procs} end\n' if constrained_indexes is not None: s += f'%{""}geom\nConstraints\n' # weird f-string to prevent python misinterpreting % for a, b in constrained_indexes: s += '{B %s %s C}\n' % (a, b) s += 'end\nend\n\n' s += '*xyz 0 1\n' for i, atom in enumerate(coords): s += '%s % .6f % .6f % .6f\n' % (pt[atomnos[i]].symbol, atom[0], atom[1], atom[2]) s += '*\n' s = ''.join(s) with open(f'{title}.inp', 'w') as f: f.write(s) try: check_call(f'{COMMANDS["ORCA"]} {title}.inp'.split(), stdout=DEVNULL, stderr=STDOUT) except KeyboardInterrupt: print('KeyboardInterrupt requested by user. Quitting.') quit() if read_output: try: opt_coords = read_xyz(f'{title}.xyz').atomcoords[0] energy = read_orca_property(f'{title}_property.txt') clean_directory() return opt_coords, energy, True except FileNotFoundError: return None, None, False
def gaussian_opt(coords, atomnos, constrained_indexes=None, method='PM6', procs=1, solvent=None, title='temp', read_output=True): ''' This function writes a Gaussian .inp file, runs it with the subprocess module and reads its output. :params coords: array of shape (n,3) with cartesian coordinates for atoms. :params atomnos: array of atomic numbers for atoms. :params constrained_indexes: array of shape (n,2), with the indexes of atomic pairs to be constrained. :params method: string, specifiyng the first line of keywords for the MOPAC input file. :params title: string, used as a file name and job title for the mopac input file. :params read_output: Whether to read the output file and return anything. ''' s = '' if MEM_GB is not None: if MEM_GB < 1: s += f'%mem={int(1000*MEM_GB)}MB\n' else: s += f'%mem={MEM_GB}GB\n' if procs > 1: s += f'%nprocshared={procs}\n' s = '# opt ' if constrained_indexes is not None else '# opt=modredundant ' s += method if solvent is not None: s += ' ' + get_solvent_line(solvent, 'GAUSSIAN', method) s += '\n\nGaussian input generated by TSCoDe\n\n0 1\n' for i, atom in enumerate(coords): s += '%s % .6f % .6f % .6f\n' % (pt[atomnos[i]].symbol, atom[0], atom[1], atom[2]) s += '\n' if constrained_indexes is not None: for a, b in constrained_indexes: s += 'B %s %s F\n' % (a + 1, b + 1 ) # Gaussian numbering starts at 1 s = ''.join(s) with open(f'{title}.com', 'w') as f: f.write(s) try: check_call(f'{COMMANDS["GAUSSIAN"]} {title}.com'.split(), stdout=DEVNULL, stderr=STDOUT) except KeyboardInterrupt: print('KeyboardInterrupt requested by user. Quitting.') quit() if read_output: try: data = read_xyz(f'{title}.out') opt_coords = data.atomcoords[0] energy = data.scfenergies[-1] * 23.060548867 # eV to kcal/mol clean_directory() return opt_coords, energy, True except FileNotFoundError: return None, None, False
def run(self): ''' Run the TSCoDe program. ''' self.write_mol_info() if self.embed is None: self.log(f'--> No embed requested, exiting.\n') self.normal_termination() if self.embed == 'error': self.log(f'--> Embed type not recognized, exiting.\n') self.normal_termination() if not self.options.let and hasattr(self, 'candidates'): assert self.candidates < 1e8, ( 'ATTENTION! This calculation is probably going to be very big. To ignore this message' ' and proceed, add the LET keyword to the input file.') self.write_options() self.t_start_run = time.perf_counter() try: # except KeyboardInterrupt try: # except ZeroCandidatesError() self.generate_candidates() if self.options.bypass: self.write_structures('unoptimized', energies=False) self.normal_termination() self.compenetration_refining() self.similarity_refining(verbose=True) if self.options.optimization: if self.options.ff_opt: self.force_field_refining() if not (self.options.ff_opt and self.options.theory_level == self.options.ff_level): # If we just optimized at a (FF) level and the final # optimization level is the same, avoid repeating it self.optimization_refining() else: self.write_structures('unoptimized', energies=False) # accounting for output in "pruned" runs with NOOPT except ZeroCandidatesError: t_end_run = time.perf_counter() s = ( ' Sorry, the program did not find any reasonable TS structure. Are you sure the input indexes and pairings were correct? If so, try these tips:\n' ' - If no structure passes the compenetration check, the SHRINK keyword may help (see documentation).\n' ' - Similarly, enlarging the spacing between atom pairs with the DIST keyword facilitates the embed.\n' ' - If no structure passes the fitness check, try adding a solvent with the SOLVENT keyword.\n' ' - Impose less strict compenetration rejection criteria with the CLASHES keyword.\n' ' - Generate more structures with higher STEPS and ROTRANGE values.\n' ) self.log( f'\n--> Program termination: No candidates found - Total time {time_to_string(t_end_run-self.t_start_run)}' ) self.log(s) self.logfile.close() clean_directory() quit() ##################### AUGMENTATION - METADYNAMICS if self.options.metadynamics: self.metadynamics_augmentation() self.optimization_refining() self.similarity_refining() ##################### POST TSCODE - SADDLE, NEB, NCI, VMD if self.options.optimization and not self.options.bypass: self.write_vmd() if self.options.neb: self.hyperneb_refining() if self.options.saddle: self.saddle_refining() if self.options.ts: self.independent_scans_refining() self.saddle_refining() if self.options.nci and self.options.optimization: self.print_nci() self.normal_termination() #### EXTRA # if self.options.debug: # path = os.path.join(os.getcwd(), self.vmd_name) # check_call(f'vmd -e {path}'.split()) ################################################ except KeyboardInterrupt: print('\n\nKeyboardInterrupt requested by user. Quitting.') quit()