Exemple #1
0
    def has_correct_imag_mode(self, calc=None, method=None):
        """Check that the imaginary mode is 'correct' set the calculation
        (hessian or optts)"""
        self.calc = calc if calc is not None else self.calc

        # By default the high level method is used to check imaginary modes
        if method is None:
            method = get_hmethod()

        # Run a fast check on  whether it's likely the mode is correct
        if not self.could_have_correct_imag_mode(method=method):
            return False

        if imag_mode_has_correct_displacement(self.calc,
                                              self.bond_rearrangement):
            logger.info('Displacement of the active atoms in the imaginary '
                        'mode bond forms and breaks the correct bonds')
            return True

        # Perform displacements over the imaginary mode to ensure the mode
        # connects reactants and products
        if imag_mode_links_reactant_products(self.calc,
                                             self.reactant,
                                             self.product,
                                             method=method):
            logger.info('Imaginary mode does link reactants and products')
            return True

        logger.warning('Species does *not* have the correct imaginary mode')
        return False
Exemple #2
0
def test_method_unavalible():

    Config.hcode = None

    Config.ORCA.path = '/an/incorrect/path'
    Config.NWChem.path = '/an/incorrect/path'
    Config.G09.path = '/an/incorrect/path'

    with pytest.raises(MethodUnavailable):
        methods.get_hmethod()

    # Specifying a method that with an executable that doesn't exist should raise an error
    Config.hcode = 'ORCA'

    with pytest.raises(MethodUnavailable):
        methods.get_hmethod()
Exemple #3
0
    def find_lowest_energy_ts_conformer(self):
        """Find the lowest energy transition state conformer by performing
        constrained optimisations"""
        atoms, energy = deepcopy(self.atoms), deepcopy(self.energy)
        calc = deepcopy(self.optts_calc)

        hmethod = get_hmethod() if Config.hmethod_conformers else None
        self.find_lowest_energy_conformer(hmethod=hmethod)

        if len(self.conformers) == 1:
            logger.warning('Only found a single conformer. '
                           'Not rerunning TS optimisation')
            self.set_atoms(atoms=atoms)
            self.energy = energy
            self.optts_calc = calc
            return None

        self.optimise(name_ext='optts_conf')

        if self.is_true_ts() and self.energy < energy:
            logger.info('Conformer search successful')

        else:
            logger.warning(f'Transition state conformer search failed '
                           f'(∆E = {energy - self.energy:.4f} Ha). Reverting')
            self.set_atoms(atoms=atoms)
            self.energy = energy
            self.optts_calc = calc
            self.imaginary_frequencies = calc.get_imaginary_freqs()

        return None
Exemple #4
0
    def could_have_correct_imag_mode(self, method=None, threshold=-45):
        """
        Determine if a point on the PES could have the correct imaginary mode. This must have

        (0) An imaginary frequency      (quoted as negative in most EST codes)
        (1) The most negative(/imaginary) is more negative that a threshold

        Keywords Arguments:
            method (autode.wrappers.base.ElectronicStructureMethod):
            threshold (float):

        Returns:
            (bool):
        """
        self._check_reactants_products()
        # By default the high level method is used to check imaginary modes
        if method is None:
            method = get_hmethod()

        if self.calc is None:
            logger.info('Calculating the hessian..')
            self.calc = Calculation(name=self.name + '_hess',
                                    molecule=self,
                                    method=method,
                                    keywords=method.keywords.hess,
                                    n_cores=Config.n_cores)
            self.calc.run()

        imag_freqs = self.calc.get_imaginary_freqs()

        if len(imag_freqs) == 0:
            logger.warning('Hessian had no imaginary modes')
            return False

        if len(imag_freqs) > 1:
            logger.warning(f'Hessian had {len(imag_freqs)} imaginary modes')

        if imag_freqs[0] > threshold:
            logger.warning('Imaginary modes were too small to be significant')
            return False

        try:
            _ = self.calc.get_normal_mode_displacements(mode_number=6)

        except NoNormalModesFound:
            logger.warning('No normal modes could be found cannot determine if'
                           'this the correct imaginary mode is found')
            return None

        # Check very conservatively for the correct displacement
        if not imag_mode_has_correct_displacement(self.calc,
                                                  self.bond_rearrangement,
                                                  delta_threshold=0.05,
                                                  req_all=False):
            logger.warning('Species does not have the correct imaginary mode')
            return False

        logger.info('Species could have the correct imaginary mode')
        return True
Exemple #5
0
    def optimise(self, name_ext='optts'):
        """Optimise this TS to a true TS """
        logger.info(f'Optimising {self.name} to a transition state')

        self._run_opt_ts_calc(method=get_hmethod(), name_ext=name_ext)

        # A transition state is a first order saddle point i.e. has a single
        # imaginary frequency
        if len(self.imaginary_frequencies) == 1:
            logger.info('Found a TS with a single imaginary frequency')
            return

        if len(self.imaginary_frequencies) == 0:
            logger.error('Transition state optimisation did not return any '
                         'imaginary frequencies')
            return

        if all([freq > -50 for freq in self.imaginary_frequencies[1:]]):
            logger.warning('Had small imaginary modes - not displacing along')
            return

        # There is more than one imaginary frequency. Will assume that the most
        # negative is the correct mode..
        for disp_magnitude in [1, -1]:
            logger.info('Displacing along second imaginary mode to try and '
                        'remove')
            dis_name_ext = name_ext + '_dis' if disp_magnitude == 1 else name_ext + '_dis2'
            atoms, energy, calc = deepcopy(self.atoms), deepcopy(
                self.energy), deepcopy(self.optts_calc)

            self.atoms = get_displaced_atoms_along_mode(
                self.optts_calc, mode_number=7, disp_magnitude=disp_magnitude)

            self._run_opt_ts_calc(method=get_hmethod(), name_ext=dis_name_ext)

            if len(self.imaginary_frequencies) == 1:
                logger.info('Displacement along second imaginary mode '
                            'successful. Now have 1 imaginary mode')
                break

            self.optts_calc = calc
            self.atoms = atoms
            self.energy = energy
            self.imaginary_frequencies = self.optts_calc.get_imaginary_freqs()

        return None
Exemple #6
0
    def optimise_reacs_prods(self):
        """Perform a geometry optimisation on all the reactants and products
        using the method"""
        h_method = get_hmethod()
        logger.info(f'Optimising reactants and products with {h_method.name}')

        for mol in self.reacs + self.prods:
            mol.optimise(h_method)

        return None
Exemple #7
0
    def calculate_single_points(self):
        """Perform a single point energy evaluations on all the reactants and
        products using the hmethod"""
        h_method = get_hmethod()
        logger.info(f'Calculating single points with {h_method.name}')

        for mol in self._reasonable_components_with_energy():
            mol.single_point(h_method)

        return None
Exemple #8
0
    def calculate_complexes(self):
        """Find the lowest energy conformers of reactant and product complexes
        using optimisation and single points"""
        h_method = get_hmethod()
        conf_hmethod = h_method if Config.hmethod_conformers else None

        for species in [self.reactant, self.product]:
            species.find_lowest_energy_conformer(hmethod=conf_hmethod)
            species.optimise(method=h_method)

        return None
Exemple #9
0
    def calculate_single_points(self):
        """Perform a single point energy evaluations on all the reactants and
        products using the hmethod"""
        h_method = get_hmethod()
        logger.info(f'Calculating single points with {h_method.name}')

        for mol in self.reacs + self.prods + [self.ts]:
            if mol is not None:
                mol.single_point(h_method)

        return None
Exemple #10
0
    def _run_hess_calculation(self, method, temp):
        """Run a Hessian calculation on this species"""
        method = method if method is not None else get_hmethod()

        calc = Calculation(name=f'{self.name}_hess',
                           molecule=self,
                           method=method,
                           keywords=method.keywords.hess,
                           n_cores=Config.n_cores,
                           temp=temp)
        calc.run()
        return calc
Exemple #11
0
    def find_lowest_energy_conformers(self):
        """Try and locate the lowest energy conformation using simulated
        annealing, then optimise them with xtb, then optimise the unique
        (defined by an energy cut-off) conformers with an electronic structure
        method"""

        h_method = get_hmethod() if Config.hmethod_conformers else None
        for mol in self.reacs + self.prods:
            # .find_lowest_energy_conformer works in conformers/
            mol.find_lowest_energy_conformer(hmethod=h_method)

        return None
Exemple #12
0
def test_free_energy_profile():

    # Use a spoofed Gaussian09 and XTB install
    Config.lcode = 'xtb'

    Config.hcode = 'g09'
    Config.G09.path = here

    Config.ts_template_folder_path = os.getcwd()

    method = get_hmethod()
    assert method.name == 'g09'

    rxn = reaction.Reaction(Reactant(name='F-', smiles='[F-]'),
                            Reactant(name='CH3Cl', smiles='ClC'),
                            Product(name='Cl-', smiles='[Cl-]'),
                            Product(name='CH3F', smiles='CF'),
                            name='sn2',
                            solvent_name='water')

    # Don't run the calculation without a working XTB install
    if shutil.which('xtb') is None or not shutil.which('xtb').endswith('xtb'):
        return

    rxn.calculate_reaction_profile(free_energy=True)

    # Allow ~0.5 kcal mol-1 either side of the true value

    dg_ts = rxn.calc_delta_g_ddagger()
    assert 17 < dg_ts * Constants.ha2kcalmol < 18

    dg_r = rxn.calc_delta_g()
    assert -13.2 < dg_r * Constants.ha2kcalmol < -12.3

    dh_ts = rxn.calc_delta_h_ddagger()
    assert 9.2 < dh_ts * Constants.ha2kcalmol < 10.3

    dh_r = rxn.calc_delta_h()
    assert -13.6 < dh_r * Constants.ha2kcalmol < -12.6

    # Should be able to plot an enthalpy profile
    plot_reaction_profile([rxn], units=KcalMol, name='enthalpy', enthalpy=True)
    assert os.path.exists('enthalpy_reaction_profile.png')
    os.remove('enthalpy_reaction_profile.png')

    # Reset the configuration to the default values
    Config.hcode = None
    Config.G09.path = None
    Config.lcode = None
    Config.XTB.path = None
Exemple #13
0
    def calculate_reaction_profile(self, units=KcalMol):
        """Calculate a multistep reaction profile using the products of step 1
        as the reactants of step 2 etc."""
        logger.info('Calculating reaction profile')
        h_method = get_hmethod()

        @work_in(self.name)
        def calculate(reaction, prev_products):

            for mol in reaction.reacs + reaction.prods:

                # If this molecule is one of the previous products don't
                # regenerate conformers
                skip_conformers = False
                for prod in prev_products:

                    # Has the same atoms & charge etc. as in the unique string
                    if str(mol) == str(prod):
                        mol = prod
                        skip_conformers = True

                if not skip_conformers:
                    mol.find_lowest_energy_conformer(
                        hmethod=h_method if Config.hmethod_conformers else None
                    )

            reaction.optimise_reacs_prods()
            reaction.find_complexes()
            reaction.locate_transition_state()
            reaction.find_lowest_energy_ts_conformer()
            reaction.calculate_single_points()

            return None

        # For all the reactions calculate the reactants, products and TS
        for i, r in enumerate(self.reactions):
            r.name = f'{self.name}_step{i}'

            if i == 0:
                # First reaction has no previous products
                calculate(reaction=r, prev_products=[])

            else:
                calculate(reaction=r,
                          prev_products=self.reactions[i - 1].prods)

        plot_reaction_profile(self.reactions, units=units, name=self.name)
        return None
Exemple #14
0
    def find_lowest_energy_ts_conformer(self, rmsd_threshold=None):
        """Find the lowest energy transition state conformer by performing
        constrained optimisations"""
        logger.info('Finding lowest energy TS conformer')

        atoms, energy = deepcopy(self.atoms), deepcopy(self.energy)
        calc = deepcopy(self.optts_calc)

        hmethod = get_hmethod() if Config.hmethod_conformers else None
        self.find_lowest_energy_conformer(hmethod=hmethod)

        # Remove similar TS conformer that are similar to this TS based on root
        # mean squared differences in their structures
        thresh = Config.rmsd_threshold if rmsd_threshold is None else rmsd_threshold
        self.conformers = [
            conf for conf in self.conformers
            if calc_heavy_atom_rmsd(conf.atoms, atoms) > thresh
        ]

        logger.info(f'Generated {len(self.conformers)} unique (RMSD > '
                    f'{thresh} Å) TS conformer(s)')

        # Optimise the lowest energy conformer to a transition state - will
        # .find_lowest_energy_conformer will have updated self.atoms etc.
        if len(self.conformers) > 0:
            self.optimise(name_ext='optts_conf')

            if self.is_true_ts() and self.energy < energy:
                logger.info('Conformer search successful')
                return None

            # Ensure the energy has a numerical value, so a difference can be
            # evaluated
            self.energy = self.energy if self.energy is not None else 0
            logger.warning(f'Transition state conformer search failed '
                           f'(∆E = {energy - self.energy:.4f} Ha). Reverting')

        logger.info('Reverting to previously found TS')
        self.atoms = atoms
        self.energy = energy
        self.optts_calc = calc
        self.imaginary_frequencies = calc.get_imaginary_freqs()

        return None
Exemple #15
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
Exemple #16
0
def test_get_hmethod():
    Config.hcode = None
    Config.ORCA.path = here       # A path that exists

    method1 = methods.get_hmethod()
    assert method1.name == 'orca'

    methods.Config.hcode = 'orca'
    method2 = methods.get_hmethod()
    assert method2.name == 'orca'

    Config.hcode = 'g09'
    Config.G09.path = here
    method3 = methods.get_hmethod()
    assert method3.name == 'g09'

    Config.hcode = 'NwChem'
    Config.NWChem.path = here
    method4 = methods.get_hmethod()
    assert method4.name == 'nwchem'

    with pytest.raises(MethodUnavailable):
        Config.hcode = 'x'
        methods.get_hmethod()
Exemple #17
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