Example #1
0
def confab_operator(filename, options, logfunction=None):
    '''
    '''

    if logfunction is not None:
        logfunction(
            f'--> Performing conformational search and optimization on {filename}'
        )

    data = read_xyz(filename)

    if len(data.atomcoords) > 1:
        raise InputError(
            f'Requested conformational search on file {filename} that already contains more than one structure.'
        )

    if len(
            tuple(
                connected_components(graphize(data.atomcoords[0],
                                              data.atomnos)))) > 1:
        raise InputError((
            f'Requested conformational search on a molecular complex (file {filename}). '
            'Confab is not suited for this task, and using TSCoDe\'s csearch> operator '
            'is a better idea.'))

    if len(set(data.atomnos) - {1, 6, 7, 8, 9, 15, 16, 17, 35, 53}) != 0:
        raise InputError((
            'Requested conformational search on a molecule that contains atoms different '
            'than the ones for which OpenBabel Force Fields are parametrized. Please '
            'perform this conformational search through the more sophisticated and better '
            'integrated csearch> operator, part of the TSCoDe program.'))

    confname = filename[:-4] + '_confab.xyz'

    with suppress_stdout_stderr():
        check_call(
            f'obabel {filename} -O {confname} --confab --rcutoff 0.5 --original'
            .split(),
            stdout=DEVNULL,
            stderr=STDOUT)
        # running Confab through Openbabel

    data = read_xyz(confname)
    conformers = data.atomcoords

    if len(conformers) > 10 and not options.let:
        conformers = conformers[0:10]
        logfunction(
            f'Will use only the best 10 conformers for TSCoDe embed.\n')

    os.remove(confname)
    with open(confname, 'w') as f:
        for i, conformer in enumerate(conformers):
            write_xyz(conformer,
                      data.atomnos,
                      f,
                      title=f'Generated conformer {i}')

    return confname
    def _set_reactive_indexes(self, filename):
        '''
        Manually set the molecule reactive atoms from the ASE GUI, imposing
        constraints on the desired atoms.

        '''
        from ase import Atoms
        from ase.gui.gui import GUI
        from ase.gui.images import Images

        data = read_xyz(filename)
        coords = data.atomcoords[0]
        labels = ''.join([pt[i].symbol for i in data.atomnos])

        atoms = Atoms(labels, positions=coords)

        while atoms.constraints == []:
            print((
                '\nPlease, manually select the reactive atom(s) for molecule %s.'
                '\nRotate with right click and select atoms by clicking. Multiple selections can be done by Ctrl+Click.'
                '\nWith desired atom(s) selected, go to Tools -> Constraints -> Constrain, then close the GUI.'
            ) % (filename))

            GUI(images=Images([atoms]), show_bonds=True).run()

        return list(atoms.constraints[0].get_indices())
Example #3
0
def opt_operator(filename, embedder, logfunction=None):
    '''
    '''

    if logfunction is not None:
        logfunction(
            f'--> Performing {embedder.options.calculator} {embedder.options.theory_level} optimization on {filename}'
        )

    t_start = time.perf_counter()

    data = read_xyz(filename)

    conformers = data.atomcoords
    energies = []

    lowest_calc = _get_lowest_calc(embedder)
    conformers, energies = _refine_structures(
        conformers,
        data.atomnos,
        *lowest_calc,
        loadstring='Optimizing conformer')

    energies, conformers = zip(
        *sorted(zip(energies, conformers), key=lambda x: x[0]))
    energies = np.array(energies) - np.min(energies)
    conformers = np.array(conformers)
    # sorting structures based on energy

    mask = energies < 10
    # getting the structures to reject (Rel Energy > 10 kcal/mol)

    if logfunction is not None:
        s = 's' if len(conformers) > 1 else ''
        s = f'Completed optimization on {len(conformers)} conformer{s}. ({time_to_string(time.perf_counter()-t_start)}).\n'

        if max(energies) > 10:
            s += f'Discarded {len(conformers)-np.count_nonzero(mask)}/{len(conformers)} unstable conformers (Rel. E. > 10 kcal/mol)\n'

    conformers, energies = conformers[mask], energies[mask]
    # applying the mask that rejects high energy confs

    optname = filename[:-4] + '_opt.xyz'
    with open(optname, 'w') as f:
        for i, conformer in enumerate(conformers):
            write_xyz(
                conformer,
                data.atomnos,
                f,
                title=
                f'Optimized conformer {i} - Rel. E. = {round(energies[i], 3)} kcal/mol'
            )

        logfunction(s + '\n')

    return optname
Example #4
0
def neb_operator(filename, embedder):
    '''
    '''
    embedder.t_start_run = time.perf_counter()
    data = read_xyz(filename)
    assert len(
        data.atomcoords
    ) == 2, 'NEB calculations need a .xyz input file with two geometries.'

    from tscode.ase_manipulations import ase_neb, ase_popt

    reagents, products = data.atomcoords
    title = filename[:-4] + '_NEB'

    embedder.log(
        f'--> Performing a NEB TS optimization. Using start and end points from {filename}\n'
        f'Theory level is {embedder.options.theory_level} via {embedder.options.calculator}'
    )

    print('Getting start point energy...', end='\r')
    _, reag_energy, _ = ase_popt(embedder, reagents, data.atomnos, steps=0)

    print('Getting end point energy...', end='\r')
    _, prod_energy, _ = ase_popt(embedder, products, data.atomnos, steps=0)

    ts_coords, ts_energy, _ = ase_neb(embedder,
                                      reagents,
                                      products,
                                      data.atomnos,
                                      title=title,
                                      logfunction=embedder.log,
                                      write_plot=True,
                                      verbose_print=True)

    e1 = ts_energy - reag_energy
    e2 = ts_energy - prod_energy

    embedder.log(
        f'NEB completed, relative energy from start/end points (not barrier heights):\n'
        f'  > E(TS)-E(start): {"+" if e1>=0 else "-"}{round(e1, 3)} kcal/mol\n'
        f'  > E(TS)-E(end)  : {"+" if e2>=0 else "-"}{round(e2, 3)} kcal/mol')

    if not (e1 > 0 and e2 > 0):
        embedder.log(
            f'\nNEB failed, TS energy is lower than both the start and end points.\n'
        )

    with open('Me_CONMe2_Mal_tetr_int_NEB_NEB_TS.xyz', 'w') as f:
        write_xyz(ts_coords,
                  data.atomnos,
                  f,
                  title='NEB TS - see log for relative energies')
Example #5
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')
    def __init__(self, filename, reactive_indexes=None, debug=False):
        '''
        Initializing class properties: reading conformational ensemble file, aligning
        conformers to first and centering them in origin.

        :params filename:           Input file name. Can be anything, .xyz preferred
        :params reactive_indexes:     Index of atoms that will link during the desired reaction.
                                    May be either int or list of int.

        :params hyper:              bool, whether to calculate orbitals positions
        '''

        if not os.path.isfile(filename):
            if '.' in filename:
                raise SyntaxError((
                    f'Molecule {filename} cannot be read. Please check your syntax.'
                ))

            raise SyntaxError((
                f'The program is trying to read something that is not a valid molecule input ({filename}). '
                +
                'If this looks like a keyword, it is probably faulted by a syntax error.'
            ))

        self.rootname = filename.split('.')[0]
        self.name = filename
        self.debug = debug

        if isinstance(reactive_indexes, np.ndarray):
            self.reactive_indexes = reactive_indexes
        else:
            self.reactive_indexes = np.array(reactive_indexes) if isinstance(
                reactive_indexes, (tuple, list)) else ()

        ccread_object = read_xyz(filename)
        if ccread_object is None:
            raise CCReadError(f'Cannot read file {filename}')

        coordinates = np.array(ccread_object.atomcoords)

        # if coordinates.shape[0] > 5:
        #     coordinates = coordinates[0:5]
        # # Do not keep more than 5 conformations

        self.atomnos = ccread_object.atomnos
        self.position = np.array([0, 0, 0],
                                 dtype=float)  # used in Embedder class
        self.rotation = np.identity(
            3)  # used in Embedder class - rotation matrix

        assert all([
            len(coordinates[i]) == len(coordinates[0])
            for i in range(1, len(coordinates))
        ]), 'Ensembles must have constant atom number.'
        # Checking that ensemble has constant length
        if self.debug:
            print(
                f'DEBUG--> Initializing object {filename}\nDEBUG--> Found {len(coordinates)} structures with {len(coordinates[0])} atoms'
            )

        self.centroid = np.sum(np.sum(coordinates, axis=0), axis=0) / (
            len(coordinates) * len(coordinates[0]))

        if self.debug:
            print('DEBUG--> Centroid was', self.centroid)

        self.atomcoords = coordinates - self.centroid
        self.graph = graphize(self.atomcoords[0], self.atomnos)
        # show_graph(self)

        self.atoms = np.array([
            atom for structure in self.atomcoords for atom in structure
        ])  # single list with all atomic positions

        if self.debug:
            print(f'DEBUG--> Total of {len(self.atoms)} atoms')
Example #7
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
Example #8
0
def csearch_operator(filename, embedder):
    '''
    '''

    embedder.log(f'--> Performing conformational search on {filename}')

    t_start = time.perf_counter()

    data = read_xyz(filename)

    if len(data.atomcoords) > 1:
        embedder.log(
            f'Requested conformational search on multimolecular file - will do\n'
            +
            'an individual search from each conformer (might be time-consuming).'
        )

    calc, method, procs = _get_lowest_calc(embedder)
    conformers = []

    for i, coords in enumerate(data.atomcoords):

        opt_coords = optimize(
            coords, data.atomnos, calculator=calc, method=method,
            procs=procs)[0] if embedder.options.optimization else coords
        # optimize starting structure before running csearch

        conf_batch = clustered_csearch(opt_coords,
                                       data.atomnos,
                                       title=f'{filename}, conformer {i+1}',
                                       logfunction=embedder.log)
        # generate the most diverse conformers starting from optimized geometry

        conformers.append(conf_batch)

    conformers = np.array(conformers)
    batch_size = conformers.shape[1]

    conformers = conformers.reshape(-1, data.atomnos.shape[0], 3)
    # merging structures from each run in a single array

    if embedder.embed is not None:
        embedder.log(
            f'\nSelected the most diverse {batch_size} out of {conformers.shape[0]} conformers for {filename} ({time_to_string(time.perf_counter()-t_start)})'
        )
        conformers = most_diverse_conformers(batch_size, conformers,
                                             data.atomnos)

    confname = filename[:-4] + '_confs.xyz'
    with open(confname, 'w') as f:
        for i, conformer in enumerate(conformers):
            write_xyz(conformer,
                      data.atomnos,
                      f,
                      title=f'Generated conformer {i}')

    # if len(conformers) > 10 and not embedder.options.let:
    #     s += f' Will use only the best 10 conformers for TSCoDe embed.'
    # embedder.log(s)

    embedder.log('\n')

    return confname
Example #9
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