Esempio n. 1
0
 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()
Esempio n. 2
0
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')
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
    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()