Beispiel #1
0
def test_get_lmethod():
    Config.lcode = None
    Config.XTB.path = here

    method3 = methods.get_lmethod()
    assert method3.name == 'xtb'

    Config.lcode = 'xtb'
    method4 = methods.get_lmethod()
    assert method4.name == 'xtb'

    Config.lcode = 'mopac'
    Config.MOPAC.path = here

    method4 = methods.get_lmethod()
    assert method4.name == 'mopac'
Beispiel #2
0
    def populate_conformers(self):
        """
        Generate and optimise with a low level method a set of conformers, the
        number of which is
        Config.num_complex_sphere_points ×  Config.num_complex_random_rotations
         ^ (n molecules in complex - 1)
        """
        n_confs = (Config.num_complex_sphere_points *
                   Config.num_complex_random_rotations *
                   (len(self.molecules) - 1))
        logger.info(f'Generating and optimising {n_confs} conformers of '
                    f'{self.name} with a low-level method')

        self._generate_conformers()

        try:
            lmethod = get_lmethod()
            for conformer in self.conformers:
                conformer.optimise(method=lmethod)
                conformer.print_xyz_file()

        except MethodUnavailable:
            logger.error('Could not optimise complex conformers')

        return None
Beispiel #3
0
def get_ts_guess_constrained_opt(reactant, method, keywords, name,
                                 distance_consts,
                                 product):
    """Get a TS guess from a constrained optimisation with the active atoms
    fixed at values defined in distance_consts

    Arguments:
        reactant (autode.complex.ReactantComplex):
        method (autode.wrappers.base.ElectronicStructureMethod):
        keywords (autode.wrappers.keywords.Keywords):
        name (str):
        distance_consts (dict): Distance constraints keyed with a tuple of atom
                                indexes and value of the distance
        product (autode.complex.ProductComplex):

    Returns:
       (autode.ts_guess.TSguess):
    """
    logger.info('Getting TS guess from constrained optimisation')

    mol_with_constraints = reactant.copy()

    # Run a low level constrained optimisation first to prevent the DFT being
    # problematic if there are >1 constraint
    l_method = get_lmethod()
    ll_const_opt = Calculation(name=f'{name}_constrained_opt_ll',
                               molecule=mol_with_constraints, method=l_method,
                               keywords=l_method.keywords.low_opt,
                               n_cores=Config.n_cores,
                               distance_constraints=distance_consts)

    # Try and set the atoms, but continue if they're not found as hopefully the
    # other method will be fine(?)
    try:
        mol_with_constraints.optimise(method=l_method, calc=ll_const_opt)

    except AtomsNotFound:
        logger.error('Failed to optimise with the low level method')

    hl_const_opt = Calculation(name=f'{name}_constrained_opt',
                               molecule=mol_with_constraints, method=method,
                               keywords=keywords, n_cores=Config.n_cores,
                               distance_constraints=distance_consts)

    # Form a transition state guess from the optimised atoms and set the
    # corresponding energy
    try:
        mol_with_constraints.optimise(method=method, calc=hl_const_opt)

    except AtomsNotFound:
        logger.error('Failed to optimise with the high level method')

    return get_ts_guess(species=mol_with_constraints, reactant=reactant,
                        product=product, name=f'ts_guess_{name}')
Beispiel #4
0
    def find_lowest_energy_conformer(self, lmethod=None, hmethod=None):
        """
        For a molecule object find the lowest conformer in energy and set the
        molecule.atoms and molecule.energy

        Arguments:
            lmethod (autode.wrappers.ElectronicStructureMethod):
            hmethod (autode.wrappers.ElectronicStructureMethod):
        """
        logger.info('Finding lowest energy conformer')

        if self.n_atoms <= 2:
            logger.warning('Cannot have conformers of a species with 2 atoms '
                           'or fewer')
            return None

        if lmethod is None:
            logger.info('Getting the default low level method')
            lmethod = get_lmethod()

        methods.add('Low energy conformers located with the')
        self._generate_conformers()

        # For all generated conformers optimise with the low level of theory
        method_string = f'and optimised using {lmethod.name}'
        if hmethod is not None:
            method_string += f' then with {hmethod.name}'
        methods.add(f'{method_string}.')

        for conformer in self.conformers:
            conformer.optimise(lmethod)

        # Strip conformers that are similar based on an energy criteria or
        # don't have an energy
        self.conformers = get_unique_confs(conformers=self.conformers)

        if hmethod is not None:
            # Re-evaluate the energy of all the conformers with the higher
            # level of theory
            for conformer in self.conformers:

                if Config.hmethod_sp_conformers:
                    assert hmethod.keywords.low_sp is not None
                    conformer.single_point(hmethod)

                else:
                    # Otherwise run a full optimisation
                    conformer.optimise(hmethod)

        self._set_lowest_energy_conformer()

        logger.info(f'Lowest energy conformer found. E = {self.energy}')
        return None
Beispiel #5
0
    def find_lowest_energy_conformer(self, lmethod=None, hmethod=None):
        """
        For a molecule object find the lowest conformer in energy and set the
        molecule.atoms and molecule.energy

        Arguments:
            lmethod (autode.wrappers.ElectronicStructureMethod):
            hmethod (autode.wrappers.ElectronicStructureMethod):
        """
        logger.info('Finding lowest energy conformer')

        if self.n_atoms <= 2:
            logger.warning('Cannot have conformers of a species with 2 atoms '
                           'or fewer')
            return None

        if lmethod is None:
            logger.info('Getting the default low level method')
            lmethod = get_lmethod()

        try:
            self._generate_conformers()
        except NotImplementedError:
            logger.error('Could not generate conformers. generate_conformers()'
                         ' not implemented')
            return None

        # For all generated conformers optimise with the low level of theory
        for conformer in self.conformers:
            conformer.optimise(lmethod)

        # Strip conformers that are similar based on an energy criteria or
        # don't have an energy
        self.conformers = get_unique_confs(conformers=self.conformers)

        if hmethod is not None:
            # Re-optimise all the conformers with the higher level of theory
            # to get more accurate energies
            for conformer in self.conformers:
                conformer.optimise(hmethod)

        self._set_lowest_energy_conformer()

        logger.info(f'Lowest energy conformer found. E = {self.energy}')
        return None
Beispiel #6
0
def get_ts_guess_function_and_params(reaction, bond_rearr):
    """Get the functions (1dscan or 2dscan) and parameters required for the
    function for a TS scan

    Arguments:
        reaction (autode.reaction.Reaction):
        bond_rearr (autode.bond_rearrangement.BondRearrangement):

    Returns:
        (list): updated funcs and params list
    """
    name = str(reaction)
    scan_name = name

    r, p = reaction.reactant, reaction.product

    lmethod, hmethod = get_lmethod(), get_hmethod()

    # Bonds with initial and final distances
    bbonds = [BreakingBond(pair, r) for pair in bond_rearr.bbonds]
    scan_name += "_".join(str(bb) for bb in bbonds)

    fbonds = [
        FormingBond(pair, r, final_species=p) for pair in bond_rearr.fbonds
    ]
    scan_name += "_".join(str(fb) for fb in fbonds)

    # Ideally use a transition state template, then only a single constrained
    # optimisation needs to be run
    yield get_template_ts_guess, (r, p, bond_rearr,
                                  f'{name}_template_{bond_rearr}', hmethod)

    # otherwise try a nudged elastic band calculation, don't use the low level
    # method if there are any metals
    if not any(atom.label in metals for atom in r.atoms):
        yield get_ts_adaptive_path, (r, p, lmethod, fbonds, bbonds,
                                     f'{name}_ll_ad_{bond_rearr}')

    # Always attempt a high-level NEB
    yield get_ts_adaptive_path, (r, p, hmethod, fbonds, bbonds,
                                 f'{name}_hl_ad_{bond_rearr}')

    return None
Beispiel #7
0
def get_ts_guess_function_and_params(reaction, bond_rearr):
    """Get the functions (1dscan or 2dscan) and parameters required for the
    function for a TS scan

    Arguments:
        reaction (autode.reaction.Reaction):
        bond_rearr (autode.bond_rearrangement.BondRearrangement):

    Returns:
        (list): updated funcs and params list
    """
    name = str(reaction)
    scan_name = name

    r, p = reaction.reactant, reaction.product

    lmethod, hmethod = get_lmethod(), get_hmethod()

    # Bonds with initial and final distances
    bbonds = [BreakingBond(pair, r, reaction) for pair in bond_rearr.bbonds]
    scan_name += "_".join(str(bb) for bb in bbonds)

    fbonds = [FormingBond(pair, r) for pair in bond_rearr.fbonds]
    scan_name += "_".join(str(fb) for fb in fbonds)

    # Ideally use a transition state template, then only a single constrained
    # optimisation needs to be run...
    yield get_template_ts_guess, (r, p, bond_rearr,
                                  f'{name}_template_{bond_rearr}', hmethod)

    # Otherwise try a nudged elastic band calculation, don't use the low level
    # method if there are any metals..
    if not any(atom.label in metals for atom in r.atoms):
        yield get_ts_guess_neb, (r, p, lmethod, fbonds, bbonds,
                                 f'{name}_ll_neb_{bond_rearr}')

    # Always attempt a high-level NEB
    yield get_ts_guess_neb, (r, p, hmethod, fbonds, bbonds,
                             f'{name}_hl_neb_{bond_rearr}')

    # Otherwise run 1D or 2D potential energy surface scans to generate a
    # transition state guess cheap -> most expensive
    if len(bbonds) == 1 and len(fbonds) == 1 and reaction.type in (Substitution, Elimination):
        yield get_ts_guess_2d, (r, p, fbonds[0], bbonds[0], f'{scan_name}_ll2d',
                                lmethod, lmethod.keywords.low_opt)

        yield get_ts_guess_1d, (r, p, bbonds[0], f'{scan_name}_hl1d_bbond',
                                hmethod, hmethod.keywords.low_opt)

        yield get_ts_guess_1d, (r, p, bbonds[0], f'{scan_name}_hl1d_alt_bbond',
                                hmethod,  hmethod.keywords.opt)

    if len(bbonds) > 0 and len(fbonds) == 1:
        yield get_ts_guess_1d, (r, p, fbonds[0], f'{scan_name}_hl1d_fbond',
                                hmethod, hmethod.keywords.low_opt)

        yield get_ts_guess_1d, (r, p, fbonds[0], f'{scan_name}_hl1d_alt_fbond',
                                hmethod, hmethod.keywords.opt)

    if len(bbonds) >= 1 and len(fbonds) >= 1:
        for fbond in fbonds:
            for bbond in bbonds:

                yield get_ts_guess_2d, (r, p, fbond, bbond,
                                        f'{scan_name}_ll2d', lmethod,
                                        lmethod.keywords.low_opt)

                yield get_ts_guess_2d, (r, p, fbond, bbond,
                                        f'{scan_name}_hl2d', hmethod,
                                        hmethod.keywords.low_opt)

    if len(bbonds) == 1 and len(fbonds) == 0:

        yield get_ts_guess_1d, (r, p, bbonds[0], f'{scan_name}_hl1d', hmethod,
                                hmethod.keywords.low_opt)

        yield get_ts_guess_1d, (r, p, bbonds[0], f'{scan_name}_hl1d_alt',
                                hmethod, hmethod.keywords.opt)

    if len(fbonds) == 2:
        yield get_ts_guess_2d, (r, p, fbonds[0], fbonds[1],
                                f'{scan_name}_ll2d_fbonds', lmethod,
                                lmethod.keywords.low_opt)
        yield get_ts_guess_2d, (r, p, fbonds[0], fbonds[1],
                                f'{scan_name}_hl2d_fbonds', hmethod,
                                hmethod.keywords.low_opt)

    if len(bbonds) == 2:
        yield get_ts_guess_2d, (r, p, bbonds[0], bbonds[1],
                                f'{scan_name}_ll2d_bbonds', lmethod,
                                lmethod.keywords.low_opt)

        yield get_ts_guess_2d, (r, p, bbonds[0], bbonds[1],
                                f'{scan_name}_hl2d_bbonds', hmethod,
                                hmethod.keywords.low_opt)

    return None
Beispiel #8
0
def get_interpolated(initial_species,
                     fbonds,
                     bbonds,
                     max_n,
                     method=None,
                     stop_thresh=0.02):
    """
    Generate the end point on the NEB by running a 1D scan, using by default a
    low-level method. Supprorts using different methods for the starting and
    final (end) points to the method used for the interpolation.
    If method is set then this will be used for both the end and intermediate
     methods

    Arguments:
        initial_species (autode.species.Species):
        fbonds (list(autode.pes.pes.FormingBond)):
        bbonds (list(autode.pes.pes.BreakingBond)):
        max_n (int): Maximum number of intermediate species to generate between
              the initial and final species

    Keyword Arguments:
        method (autode.wrappers.base.ElectronicStructureMethod):
        stop_thresh (float): Energy threshold in Ha to terminate the
                    interpolation if ∆E between two adjacent points is > this
                    and there is a peak in the surface, return the points.
                    default is ~ 10 kcal mol-1

    Returns:
        (list(autode.species.Species)): Set of intermediate species between
    """
    assert fbonds is not None and bbonds is not None
    logger.info('Generating the interpolated species reactant -> product using'
                f' a maximum of {max_n} intermediate points')

    bonds = active_bonds_no_rings(initial_species, fbonds, bbonds)

    # Calculate the uniform change in each bond distance from initial -> final
    deltas = [(b.final_dist - b.curr_dist) / (max_n - 1) for b in bonds]

    # Set a dictionary of bond length constraints
    consts = {b.atom_indexes: b.curr_dist for b in bonds}

    species_set = []

    # Generate a species with a constrained geometry for each point in the path
    for i in range(max_n):

        if i == 0:
            species = initial_species.copy()

        else:
            species = species_set[i - 1].copy()

            # Add the required change in every bond length to get to the final
            # distances at step n-1
            for j, atom_indexes in enumerate(consts.keys()):
                consts[atom_indexes] += deltas[j]

        # Run the constrained optimisation
        if method is None:
            method = get_lmethod()

        opt = Calculation(name=f'{species.name}_constrained_opt{i}',
                          molecule=species,
                          method=method,
                          keywords=method.keywords.opt,
                          n_cores=Config.n_cores,
                          distance_constraints=consts)

        # Set the optimised atoms - can raise AtomsNotFound
        species.optimise(method=method, calc=opt)
        species_set.append(species)

        # Early stopping if a ~saddle point has already been traversed, must be
        # in the second half of the scan and above an energy threshold for ∆E
        if all((i > 1, i > max_n // 2, contains_peak(species_set),
                species.energy - species_set[i - 1].energy > stop_thresh)):

            logger.warning(f'Path contained an energy peak and the point '
                           f'before this one had a lower energy - stopping the'
                           f' interpolation on step {i}')

            return species_set

    logger.info('Generated initial NEB path')
    return species_set
Beispiel #9
0
def imag_mode_links_reactant_products(calc,
                                      reactant,
                                      product,
                                      method,
                                      disp_mag=1.0):
    """Displaces atoms along the imaginary mode forwards (f) and backwards (b)
    to see if products and reactants are made

    Arguments:
        calc (autode.calculation.Calculation):
        reactant (autode.complex.ReactantComplex):
        product (autode.complex.ProductComplex):
        method (autode.wrappers.base.ElectronicStructureMethod):

    Keyword Arguments:
        disp_mag (int): Distance to be displaced along the imag mode
                     (default: )

    Returns:
        (bool): if the imag mode is correct or not
    """
    logger.info('Displacing along imag modes to check that the TS links '
                'reactants and products')

    # Generate and optimise conformers with the low level of theory
    reactant.populate_conformers()
    product.populate_conformers()

    # Get the species that is optimised by displacing forwards along the mode
    f_displaced_atoms = get_displaced_atoms_along_mode(calc,
                                                       mode_number=6,
                                                       disp_magnitude=disp_mag)
    f_displaced_mol = get_optimised_species(calc,
                                            method,
                                            direction='forwards',
                                            atoms=f_displaced_atoms)

    # Get the species that is optimised by displacing backwards along the mode
    b_displaced_atoms = get_displaced_atoms_along_mode(
        calc, mode_number=6, disp_magnitude=-disp_mag)
    b_displaced_mol = get_optimised_species(calc,
                                            method,
                                            direction='backwards',
                                            atoms=b_displaced_atoms)

    if f_b_isomorphic_to_r_p(f_displaced_mol, b_displaced_mol, reactant,
                             product):
        return True

    # The high and low level methods may not have the same minima, so optimise
    #  and recheck isomorphisms
    for mol in (f_displaced_mol, b_displaced_mol):
        mol.optimise(method=get_lmethod(), reset_graph=True)

    if f_b_isomorphic_to_r_p(f_displaced_mol, b_displaced_mol, reactant,
                             product):
        return True

    logger.info(f'Forwards displaced edges {f_displaced_mol.graph.edges}')
    logger.info(f'Backwards displaced edges {b_displaced_mol.graph.edges}')
    return False