Exemplo n.º 1
0
def test_constraints():
    os.chdir(os.path.join(here, 'data'))

    calc = Calculation(name='const_dist_opt',
                       molecule=test_mol,
                       method=method,
                       keywords=opt_keywords,
                       distance_constraints={(0, 1): 1.2})
    calc.run()
    opt_atoms = calc.get_final_atoms()

    assert 1.199 < np.linalg.norm(opt_atoms[0].coord -
                                  opt_atoms[1].coord) < 1.201

    calc = Calculation(name='const_cart_opt',
                       molecule=test_mol,
                       method=method,
                       keywords=opt_keywords,
                       cartesian_constraints=[0])
    calc.run()
    opt_atoms = calc.get_final_atoms()
    assert np.linalg.norm(test_mol.atoms[0].coord - opt_atoms[0].coord) < 1E-3

    os.remove('const_cart_opt_g09.com')
    os.remove('const_dist_opt_g09.com')
    os.chdir(os.path.join(here))
Exemplo n.º 2
0
def test_bad_gauss_output():

    calc = Calculation(name='no_output', molecule=test_mol, method=method,
                       keywords=opt_keywords)
    calc.output_file_lines = []
    calc.rev_output_file_lines = []

    assert calc.get_energy() is None
    with pytest.raises(AtomsNotFound):
        calc.get_final_atoms()

    with pytest.raises(NoInputError):
        calc.execute_calculation()
Exemplo n.º 3
0
def test_constraints():

    calc = Calculation(name='const_dist_opt', molecule=test_mol, method=method,
                       keywords=opt_keywords, distance_constraints={(0, 1): 1.2})
    calc.run()
    opt_atoms = calc.get_final_atoms()

    assert 1.199 < np.linalg.norm(opt_atoms[0].coord - opt_atoms[1].coord) < 1.201

    calc = Calculation(name='const_cart_opt', molecule=test_mol, method=method,
                       keywords=opt_keywords, cartesian_constraints=[0])
    calc.run()
    opt_atoms = calc.get_final_atoms()
    assert np.linalg.norm(test_mol.atoms[0].coord - opt_atoms[0].coord) < 1E-3
Exemplo n.º 4
0
def test_xtb_calculation():

    test_mol = Molecule(name='test_mol',
                        smiles='O=C(C=C1)[C@@](C2NC3C=C2)([H])[C@@]3([H])C1=O')
    calc = Calculation(name='opt', molecule=test_mol, method=method,
                       keywords=Config.XTB.keywords.opt)
    calc.run()

    assert os.path.exists('opt_xtb.xyz') is True
    assert os.path.exists('opt_xtb.out') is True
    assert len(calc.get_final_atoms()) == 22
    assert calc.get_energy() == -36.990267613593
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.input.filename == 'opt_xtb.xyz'
    assert calc.output.filename == 'opt_xtb.out'

    with pytest.raises(NotImplementedError):
        calc.optimisation_nearly_converged()
    with pytest.raises(NotImplementedError):
        calc.get_imaginary_freqs()
    with pytest.raises(NotImplementedError):
        calc.get_normal_mode_displacements(4)

    charges = calc.get_atomic_charges()
    assert len(charges) == 22
    assert all(-1.0 < c < 1.0 for c in charges)

    const_opt = Calculation(name='const_opt', molecule=test_mol,
                            method=method,
                            distance_constraints={(0, 1): 1.2539792},
                            cartesian_constraints=[0],
                            keywords=Config.XTB.keywords.opt)

    const_opt.generate_input()
    assert os.path.exists('const_opt_xtb.xyz')
    assert os.path.exists('xcontrol_const_opt_xtb')

    const_opt.clean_up(force=True)
    assert not os.path.exists('xcontrol_const_opt_xtb')

    # Write an empty output file
    open('tmp.out', 'w').close()
    const_opt.output.filename = 'tmp.out'
    const_opt.output.set_lines()

    # cannot get atoms from an empty file
    with pytest.raises(AtomsNotFound):
        _ = const_opt.get_final_atoms()
Exemplo n.º 5
0
def test_mopac_opt_calculation():

    calc = Calculation(name='opt',
                       molecule=methylchloride,
                       method=method,
                       keywords=Config.MOPAC.keywords.opt)
    calc.run()

    assert os.path.exists('opt_mopac.mop') is True
    assert os.path.exists('opt_mopac.out') is True
    assert len(calc.get_final_atoms()) == 5

    # Actual energy in Hartrees
    energy = Constants.eV2ha * -430.43191
    assert energy - 0.0001 < calc.get_energy() < energy + 0.0001

    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.input.filename == 'opt_mopac.mop'
    assert calc.output.filename == 'opt_mopac.out'
    assert calc.terminated_normally()
    assert calc.optimisation_converged() is True

    with pytest.raises(CouldNotGetProperty):
        _ = calc.get_gradients()

    with pytest.raises(NotImplementedError):
        _ = calc.optimisation_nearly_converged()
    with pytest.raises(NotImplementedError):
        _ = calc.get_imaginary_freqs()
    with pytest.raises(NotImplementedError):
        _ = calc.get_normal_mode_displacements(4)
Exemplo n.º 6
0
    def optimise(self, method, reset_graph=False, calc=None):
        """
        Optimise the geometry of this conformer

        Arguments:
            method (autode.wrappers.base.ElectronicStructureMethod):

        Keyword Arguments:
            reset_graph (bool):
            calc (autode.calculation.Calculation):
        """
        logger.info(f'Running optimisation of {self.name}')

        if calc is not None or reset_graph:
            raise NotImplementedError

        opt = Calculation(name=f'{self.name}_opt',
                          molecule=self,
                          method=method,
                          keywords=method.keywords.low_opt,
                          n_cores=Config.n_cores,
                          distance_constraints=self.dist_consts)
        opt.run()
        self.energy = opt.get_energy()

        try:
            self.set_atoms(atoms=opt.get_final_atoms())

        except AtomsNotFound:
            logger.error(f'Atoms not found for {self.name} but not critical')
            self.set_atoms(atoms=None)

        return None
Exemplo n.º 7
0
def test_opt_calc():

    calc = Calculation(name='opt',
                       molecule=test_mol,
                       method=method,
                       keywords=opt_keywords)
    calc.run()

    assert os.path.exists('opt_nwchem.nw')
    assert os.path.exists('opt_nwchem.out')

    final_atoms = calc.get_final_atoms()
    assert len(final_atoms) == 5
    assert type(final_atoms[0]) is Atom
    assert -40.4165 < calc.get_energy() < -40.4164
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.get_imaginary_freqs() == []
    assert calc.input.filename == 'opt_nwchem.nw'
    assert calc.output.filename == 'opt_nwchem.out'
    assert calc.terminated_normally()
    assert calc.optimisation_converged()
    assert calc.optimisation_nearly_converged() is False

    charges = calc.get_atomic_charges()
    assert len(charges) == 5
    assert all(-1.0 < c < 1.0 for c in charges)

    # Optimisation should result in small gradients
    gradients = calc.get_gradients()
    assert len(gradients) == 5
    assert all(-0.1 < np.linalg.norm(g) < 0.1 for g in gradients)
Exemplo n.º 8
0
    def optimise(self, method=None, reset_graph=False, calc=None):
        """
        Optimise the geometry using a method

        Arguments:
            method (autode.wrappers.base.ElectronicStructureMethod):

        Keyword Arguments:
            reset_graph (bool): Reset the molecular graph
            calc (autode.calculation.Calculation): Different e.g. constrained
                                                   optimisation calculation
        """
        logger.info(f'Running optimisation of {self.name}')

        if calc is None:
            assert method is not None

            calc = Calculation(name=f'{self.name}_opt', molecule=self,
                               method=method, keywords=method.keywords.opt,
                               n_cores=Config.n_cores)
        else:
            assert isinstance(calc, Calculation)

        calc.run()
        self.energy = calc.get_energy()
        self.set_atoms(atoms=calc.get_final_atoms())
        self.print_xyz_file(filename=f'{self.name}_optimised_{method.name}.xyz')

        if reset_graph:
            make_graph(self)

        return None
Exemplo n.º 9
0
def test_psi4_opt_calculation():

    methylchloride = Molecule(name='CH3Cl',
                              smiles='[H]C([H])(Cl)[H]',
                              solvent_name='water')

    calc = Calculation(name='opt',
                       molecule=methylchloride,
                       method=method,
                       keywords=opt_keywords)
    calc.run()

    assert os.path.exists('opt_psi4.inp') is True
    assert os.path.exists('opt_orca.out') is True
    assert len(calc.get_final_atoms()) == 5
    assert -499.735 < calc.get_energy() < -499.730
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.get_imaginary_freqs() == []
    assert calc.input.filename == 'opt_psi4.inp'
    assert calc.output.filename == 'opt_psi4.out'
    assert calc.terminated_normally()

    assert calc.optimisation_converged()

    calc = Calculation(name='opt',
                       molecule=methylchloride,
                       method=method,
                       keywords=opt_keywords)

    # If the calculation is not run with calc.run() then there should be no
    # input and the calc should raise that there is no input

    with pytest.raises(NoInputError):
        execute_calc(calc)
Exemplo n.º 10
0
def get_optimised_species(calc, method, direction, atoms):
    """Get the species that is optimised from an initial set of atoms"""

    species = Molecule(name=f'{calc.name}_{direction}',
                       atoms=atoms,
                       charge=calc.molecule.charge,
                       mult=calc.molecule.mult)

    # Note that for the surface to be the same the keywords.opt and keywords.hess need to match in the level of theory
    calc = Calculation(name=f'{calc.name}_{direction}',
                       molecule=species,
                       method=method,
                       keywords=method.keywords.opt,
                       n_cores=Config.n_cores)
    calc.run()

    try:
        species.set_atoms(atoms=calc.get_final_atoms())
        species.energy = calc.get_energy()
        make_graph(species)

    except AtomsNotFound:
        logger.error(f'{direction} displacement calculation failed')

    return species
Exemplo n.º 11
0
def test_xtb_6_1_old():

    mol = Molecule(name='methane', smiles='C')
    calc = Calculation(name='test',
                       molecule=mol,
                       method=method,
                       keywords=method.keywords.opt)

    for filename in ('xtb_6_1_opt.out', 'xtb_no_version_opt.out'):

        calc.output.filename = filename
        calc.output.set_lines()

        assert len(calc.get_final_atoms()) == 5
        mol.atoms = calc.get_final_atoms()

        assert set([atom.label for atom in mol.atoms]) == {'C', 'H'}
        assert 0.9 < mol.distance(0, 1) < 1.2

        calc.output.set_lines()
Exemplo n.º 12
0
def test_xtb_6_3_2():

    mol = Molecule(name='CH3Cl', smiles='ClC')
    calc = Calculation(name='test',
                       molecule=mol,
                       method=method,
                       keywords=method.keywords.opt)

    calc.output.filename = 'xtb_6_3_2_opt.out'
    calc.output.file_lines = open('xtb_6_3_2_opt.out', 'r').readlines()

    assert len(calc.get_final_atoms()) == 5
Exemplo n.º 13
0
def test_xtb_6_3_2():

    mol = Molecule(name='CH3Cl', smiles='ClC')
    calc = Calculation(name='test',
                       molecule=mol,
                       method=method,
                       keywords=method.keywords.opt)

    out_path = os.path.join(here, 'data', 'xtb', 'xtb_6_3_2_opt.out')

    calc.output.filename = out_path
    calc.output.file_lines = open(out_path, 'r').readlines()

    assert len(calc.get_final_atoms()) == 5
Exemplo n.º 14
0
def test_mopac_with_pc():

    calc = Calculation(name='opt_pc', molecule=methylchloride,
                       method=method,
                       keywords=Config.MOPAC.keywords.opt,
                       point_charges=[PointCharge(1, x=4, y=4, z=4)])
    calc.run()

    assert os.path.exists('opt_pc_mopac.mop') is True
    assert os.path.exists('opt_pc_mopac.out') is True
    assert len(calc.get_final_atoms()) == 5

    # Actual energy in Hartrees without any point charges
    energy = Constants.eV2ha * -430.43191
    assert np.abs(calc.get_energy() - energy) > 0.0001
Exemplo n.º 15
0
def test_gauss_opt_calc():

    os.chdir(os.path.join(here, 'data'))

    methylchloride = Molecule(name='CH3Cl',
                              smiles='[H]C([H])(Cl)[H]',
                              solvent_name='water')
    calc = Calculation(name='opt',
                       molecule=methylchloride,
                       method=method,
                       keywords=opt_keywords)
    calc.run()

    assert os.path.exists('opt_g09.com')
    assert os.path.exists('opt_g09.log')
    assert len(calc.get_final_atoms()) == 5
    assert os.path.exists('opt_g09.xyz')
    assert calc.get_energy() == -499.729222331
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.get_imaginary_freqs() == []

    with pytest.raises(NoNormalModesFound):
        calc.get_normal_mode_displacements(mode_number=1)

    assert calc.input.filename == 'opt_g09.com'
    assert calc.output.filename == 'opt_g09.log'
    assert calc.terminated_normally()
    assert calc.optimisation_converged()
    assert calc.optimisation_nearly_converged() is False

    charges = calc.get_atomic_charges()
    assert len(charges) == methylchloride.n_atoms

    # Should be no very large atomic charges in this molecule
    assert all(-1.0 < c < 1.0 for c in charges)

    gradients = calc.get_gradients()
    assert len(gradients) == methylchloride.n_atoms
    assert len(gradients[0]) == 3

    # Should be no large forces for an optimised molecule
    assert sum(gradients[0]) < 0.1

    os.remove('opt_g09.com')
    os.chdir(here)
Exemplo n.º 16
0
def test_orca_opt_calculation():

    os.chdir(os.path.join(here, 'data'))

    methylchloride = Molecule(name='CH3Cl',
                              smiles='[H]C([H])(Cl)[H]',
                              solvent_name='water')

    calc = Calculation(name='opt', molecule=methylchloride, method=method,
                       keywords=opt_keywords)
    calc.run()

    assert os.path.exists('opt_orca.inp') is True
    assert os.path.exists('opt_orca.out') is True
    assert len(calc.get_final_atoms()) == 5
    assert -499.735 < calc.get_energy() < -499.730
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.get_imaginary_freqs() == []
    assert calc.input.filename == 'opt_orca.inp'
    assert calc.output.filename == 'opt_orca.out'
    assert calc.terminated_normally()

    assert calc.optimisation_converged()

    assert calc.optimisation_nearly_converged() is False

    with pytest.raises(NoNormalModesFound):
        calc.get_normal_mode_displacements(mode_number=0)

    # Should have a partial atomic charge for every atom
    charges = calc.get_atomic_charges()
    assert len(charges) == 5
    assert type(charges[0]) == float
    assert -1.0 < charges[0] < 1.0

    calc = Calculation(name='opt', molecule=methylchloride, method=method,
                       keywords=opt_keywords)

    # If the calculation is not run with calc.run() then there should be no
    # input and the calc should raise that there is no input
    with pytest.raises(NoInputError):
        execute_calc(calc)

    os.remove('opt_orca.inp')
    os.chdir(here)
Exemplo n.º 17
0
def test_opt_hf_constraints():

    keywords = OptKeywords([
        'driver\n gmax 0.002\n  grms 0.0005\n'
        '  xmax 0.01\n   xrms 0.007\n  eprec 0.00003\nend',
        'basis\n  *   library Def2-SVP\nend', 'task scf optimize'
    ])

    h2o = Molecule(name='water', smiles='O')
    calc = Calculation(name='opt_water',
                       molecule=h2o,
                       method=method,
                       keywords=keywords,
                       cartesian_constraints=[0],
                       distance_constraints={(0, 1): 0.95})
    calc.run()
    h2o.atoms = calc.get_final_atoms()
    assert 0.94 < h2o.distance(0, 1) < 0.96
Exemplo n.º 18
0
def test_xtb_calculation():

    os.chdir(os.path.join(here, 'data'))
    XTB.available = True

    test_mol = Molecule(name='test_mol',
                        smiles='O=C(C=C1)[C@@](C2NC3C=C2)([H])[C@@]3([H])C1=O')
    calc = Calculation(name='opt', molecule=test_mol, method=method,
                       keywords=Config.XTB.keywords.opt)
    calc.run()

    assert os.path.exists('opt_xtb.xyz') is True
    assert os.path.exists('opt_xtb.out') is True
    assert len(calc.get_final_atoms()) == 22
    assert calc.get_energy() == -36.990267613593
    assert calc.output.exists()
    assert calc.output.file_lines is not None
    assert calc.input.filename == 'opt_xtb.xyz'
    assert calc.output.filename == 'opt_xtb.out'

    with pytest.raises(NotImplementedError):
        calc.optimisation_nearly_converged()
    with pytest.raises(NotImplementedError):
        calc.get_imaginary_freqs()
    with pytest.raises(NotImplementedError):
        calc.get_normal_mode_displacements(4)

    charges = calc.get_atomic_charges()
    assert len(charges) == 22
    assert all(-1.0 < c < 1.0 for c in charges)

    const_opt = Calculation(name='const_opt', molecule=test_mol,
                            method=method,
                            distance_constraints={(0, 1): 1.2539792},
                            cartesian_constraints=[0],
                            keywords=Config.XTB.keywords.opt)

    const_opt.generate_input()
    assert os.path.exists('xcontrol_const_opt_xtb')

    os.remove('const_opt_xtb.xyz')
    os.remove('xcontrol_const_opt_xtb')
    os.remove('opt_xtb.xyz')
    os.chdir(here)
Exemplo n.º 19
0
def test_opt_hf_constraints():
    os.chdir(os.path.join(here, 'data'))

    keywords = OptKeywords([
        'driver\n gmax 0.002\n  grms 0.0005\n'
        '  xmax 0.01\n   xrms 0.007\n  eprec 0.00003\nend',
        'basis\n  *   library Def2-SVP\nend', 'task scf optimize'
    ])

    h2o = Molecule(name='water', smiles='O')
    calc = Calculation(name='opt_water',
                       molecule=h2o,
                       method=method,
                       keywords=keywords,
                       cartesian_constraints=[0],
                       distance_constraints={(0, 1): 0.95})
    calc.run()
    h2o.set_atoms(atoms=calc.get_final_atoms())
    assert 0.94 < h2o.get_distance(0, 1) < 0.96

    os.remove('opt_water_nwchem.nw')

    os.chdir(here)
Exemplo n.º 20
0
class TransitionState(TSbase):
    @requires_graph()
    def _update_graph(self):
        """Update the molecular graph to include all the bonds that are being
        made/broken"""
        if self.bond_rearrangement is None:
            logger.warning('Bond rearrangement not set - molecular graph '
                           'updating with no active bonds')
            active_bonds = []

        else:
            active_bonds = self.bond_rearrangement.all

        set_active_mol_graph(species=self, active_bonds=active_bonds)

        logger.info(f'Molecular graph updated with active bonds')
        return None

    def _run_opt_ts_calc(self, method, name_ext):
        """Run an optts calculation and attempt to set the geometry, energy and
         normal modes"""
        if self.bond_rearrangement is None:
            logger.warning('Cannot add redundant internal coordinates for the '
                           'active bonds with no bond rearrangement')
            bond_ids = None

        else:
            bond_ids = self.bond_rearrangement.all

        self.optts_calc = Calculation(
            name=f'{self.name}_{name_ext}',
            molecule=self,
            method=method,
            n_cores=Config.n_cores,
            keywords=method.keywords.opt_ts,
            bond_ids_to_add=bond_ids,
            other_input_block=method.keywords.optts_block)
        self.optts_calc.run()

        if not self.optts_calc.optimisation_converged():
            if self.optts_calc.optimisation_nearly_converged():
                logger.info('Optimisation nearly converged')
                self.calc = self.optts_calc
                if self.could_have_correct_imag_mode():
                    logger.info('Still have correct imaginary mode, trying '
                                'more  optimisation steps')

                    self.atoms = self.optts_calc.get_final_atoms()
                    self.optts_calc = Calculation(
                        name=f'{self.name}_{name_ext}_reopt',
                        molecule=self,
                        method=method,
                        n_cores=Config.n_cores,
                        keywords=method.keywords.opt_ts,
                        bond_ids_to_add=bond_ids,
                        other_input_block=method.keywords.optts_block)
                    self.optts_calc.run()
                else:
                    logger.info('Lost imaginary mode')
            else:
                logger.info('Optimisation did not converge')

        try:
            self.imaginary_frequencies = self.optts_calc.get_imaginary_freqs()
            self.atoms = self.optts_calc.get_final_atoms()
            self.energy = self.optts_calc.get_energy()

        except (AtomsNotFound, NoNormalModesFound):
            logger.error('Transition state optimisation calculation failed')

        return

    def _generate_conformers(self, n_confs=None):
        """Generate conformers at the TS """
        from autode.conformers.conformer import Conformer
        from autode.conformers.conf_gen import get_simanl_atoms
        from autode.conformers.conformers import conf_is_unique_rmsd

        n_confs = Config.num_conformers if n_confs is None else n_confs
        self.conformers = []

        distance_consts = get_distance_constraints(self)

        with Pool(processes=Config.n_cores) as pool:
            results = [
                pool.apply_async(get_simanl_atoms, (self, distance_consts, i))
                for i in range(n_confs)
            ]

            conf_atoms_list = [res.get(timeout=None) for res in results]

        for i, atoms in enumerate(conf_atoms_list):
            conf = Conformer(name=f'{self.name}_conf{i}',
                             charge=self.charge,
                             mult=self.mult,
                             atoms=atoms,
                             dist_consts=distance_consts)

            # If the conformer is unique on an RMSD threshold
            if conf_is_unique_rmsd(conf, self.conformers):
                conf.solvent = self.solvent
                conf.graph = deepcopy(self.graph)
                self.conformers.append(conf)

        logger.info(f'Generated {len(self.conformers)} conformer(s)')
        return None

    @requires_atoms()
    def print_imag_vector(self, mode_number=6, name=None):
        """Print a .xyz file with multiple structures visualising the largest
        magnitude imaginary mode

        Keyword Arguments:
            mode_number (int): Number of the normal mode to visualise,
                               6 (default) is the lowest frequency vibration
                               i.e. largest magnitude imaginary, if present
            name (str):
        """
        assert self.optts_calc is not None
        name = self.name if name is None else name

        disp = -0.5
        for i in range(40):
            atoms = get_displaced_atoms_along_mode(
                calc=self.optts_calc,
                mode_number=int(mode_number),
                disp_magnitude=disp,
                atoms=self.atoms)
            atoms_to_xyz_file(atoms=atoms, filename=f'{name}.xyz', append=True)

            # Add displacement so the final set of atoms are +0.5 Å displaced
            # along the mode, then displaced back again
            sign = 1 if i < 20 else -1
            disp += sign * 1.0 / 20.0

        return None

    @requires_atoms()
    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

    def calc_g_cont(self, method=None, calc=None, temp=None):
        """Calculate the free energy (G) contribution"""
        return super().calc_g_cont(method=method,
                                   calc=self.optts_calc,
                                   temp=temp)

    def calc_h_cont(self, method=None, calc=None, temp=None):
        """Calculate the enthalpy (H) contribution"""
        return super().calc_h_cont(method=method,
                                   calc=self.optts_calc,
                                   temp=temp)

    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

    def is_true_ts(self):
        """Is this TS a 'true' TS i.e. has at least on imaginary mode in the
        hessian and is the correct mode"""

        if self.energy is None:
            logger.warning('Cannot be true TS with no energy')
            return False

        if len(self.imaginary_frequencies) > 0:
            if self.has_correct_imag_mode(calc=self.optts_calc):
                logger.info('Found a transition state with the correct '
                            'imaginary mode & links reactants and products')
                return True

        return False

    def save_ts_template(self, folder_path=None):
        """Save a transition state template containing the active bond lengths,
         solvent and charge in folder_path

        Keyword Arguments:
            folder_path (str): folder to save the TS template to
            (default: {None})
        """
        if self.bond_rearrangement is None:
            raise ValueError('Cannot save a TS template without a bond '
                             'rearrangement')

        logger.info(f'Saving TS template for {self.name}')

        truncated_graph = get_truncated_active_mol_graph(self.graph)

        for bond in self.bond_rearrangement.all:
            truncated_graph.edges[bond]['distance'] = self.distance(*bond)

        ts_template = TStemplate(truncated_graph, species=self)
        ts_template.save(folder_path=folder_path)

        logger.info('Saved TS template')
        return None

    def __init__(self, ts_guess):
        """
        Transition State

        Arguments:
            ts_guess (autode.transition_states.ts_guess.TSguess):
        """
        super().__init__(atoms=ts_guess.atoms,
                         reactant=ts_guess.reactant,
                         product=ts_guess.product,
                         name=f'TS_{ts_guess.name}',
                         charge=ts_guess.charge,
                         mult=ts_guess.mult)

        self.bond_rearrangement = ts_guess.bond_rearrangement
        self.conformers = None

        self.optts_calc = None
        self.imaginary_frequencies = []

        self._update_graph()
Exemplo n.º 21
0
def test_calc_class():

    xtb = XTB()

    calc = Calculation(name='-tmp',
                       molecule=test_mol,
                       method=xtb,
                       keywords=xtb.keywords.sp)

    # Should prepend a dash to appease some EST methods
    assert not calc.name.startswith('-')
    assert calc.molecule is not None
    assert calc.method.name == 'xtb'

    assert calc.get_energy() is None
    assert calc.get_enthalpy() is None
    assert calc.get_free_energy() is None

    assert not calc.optimisation_converged()
    assert not calc.optimisation_nearly_converged()

    with pytest.raises(ex.AtomsNotFound):
        _ = calc.get_final_atoms()

    with pytest.raises(ex.CouldNotGetProperty):
        _ = calc.get_gradients()

    with pytest.raises(ex.CouldNotGetProperty):
        _ = calc.get_atomic_charges()

    # Calculation that has not been run shouldn't have an opt converged
    assert not calc.optimisation_converged()
    assert not calc.optimisation_nearly_converged()

    # With a filename that doesn't exist a NoOutput exception should be raised
    calc.output.filename = '/a/path/that/does/not/exist/tmp'
    with pytest.raises(ex.NoCalculationOutput):
        calc.output.set_lines()

    # With no output should not be able to get properties
    calc.output.filename = 'tmp'
    calc.output.file_lines = []
    with pytest.raises(ex.CouldNotGetProperty):
        _ = calc.get_atomic_charges()

    # or final atoms
    with pytest.raises(ex.AtomsNotFound):
        _ = calc.get_final_atoms()

    # Should default to a single core
    assert calc.n_cores == 1

    calc_str = str(calc)
    new_calc = Calculation(name='tmp2',
                           molecule=test_mol,
                           method=xtb,
                           keywords=xtb.keywords.sp)
    new_calc_str = str(new_calc)
    # Calculation strings need to be unique
    assert new_calc_str != calc_str

    new_calc = Calculation(name='tmp2',
                           molecule=test_mol,
                           method=xtb,
                           keywords=xtb.keywords.sp,
                           temp=5000)
    assert str(new_calc) != new_calc_str

    mol_no_atoms = Molecule()
    with pytest.raises(ex.NoInputError):
        _ = Calculation(name='tmp2',
                        molecule=mol_no_atoms,
                        method=xtb,
                        keywords=xtb.keywords.sp)
Exemplo n.º 22
0
def optimise(molecule,
             method,
             keywords,
             n_cores=None,
             cartesian_constraints=None):
    """
    Optimise a molecule

    :param molecule: (object)
    :param method: (autode.ElectronicStructureMethod)
    :param keywords: (list(str)) Keywords to use for the electronic structure c
                                 alculation e.g. ['Opt', 'PBE', 'def2-SVP']
    :param n_cores: (int) Number of cores to use
    :param cartesian_constraints: (list(int)) List of atom ids to constrain
    :return:
    """
    logger.info('Running an optimisation calculation')

    n_cores = Config.n_cores if n_cores is None else int(n_cores)

    try:
        from autode.calculation import Calculation
        from autode.wrappers.XTB import xtb
        from autode.wrappers.ORCA import orca
        from autode.wrappers.keywords import OptKeywords

    except ModuleNotFoundError:
        logger.error('autode not found. Calculations not available')
        raise RequiresAutodE

    if keywords is None:
        if method == orca:

            keywords = OptKeywords(['LooseOpt', 'PBE', 'D3BJ', 'def2-SVP'])
            logger.warning(f'No keywords were set for the optimisation but an'
                           f' ORCA calculation was requested. '
                           f'Using {str(keywords)}')

        elif method == xtb:
            keywords = xtb.keywords.opt

        else:
            logger.critical('No keywords were set for the optimisation '
                            'calculation')
            raise Exception

    else:
        # If the keywords are specified as a list convert them to a set of
        # OptKeywords, required for autodE
        if type(keywords) is list:
            keywords = OptKeywords(keywords)

    opt = Calculation(name=molecule.name + '_opt',
                      molecule=molecule,
                      method=method,
                      keywords=keywords,
                      n_cores=n_cores,
                      cartesian_constraints=cartesian_constraints)
    opt.run()
    molecule.energy = opt.get_energy()
    molecule.set_atoms(atoms=opt.get_final_atoms())

    return None
Exemplo n.º 23
0
class TransitionState(TSbase):

    @requires_graph()
    def _update_graph(self):
        """Update the molecular graph to include all the bonds that are being
        made/broken"""
        set_active_mol_graph(species=self, active_bonds=self.bond_rearrangement.all)

        logger.info(f'Molecular graph updated with '
                    f'{len(self.bond_rearrangement.all)} active bonds')
        return None

    def _run_opt_ts_calc(self, method, name_ext):
        """Run an optts calculation and attempt to set the geometry, energy and
         normal modes"""

        self.optts_calc = Calculation(name=f'{self.name}_{name_ext}',
                                      molecule=self,
                                      method=method,
                                      n_cores=Config.n_cores,
                                      keywords=method.keywords.opt_ts,
                                      bond_ids_to_add=self.bond_rearrangement.all,
                                      other_input_block=method.keywords.optts_block)
        self.optts_calc.run()

        if not self.optts_calc.optimisation_converged():
            if self.optts_calc.optimisation_nearly_converged():
                logger.info('Optimisation nearly converged')
                self.calc = self.optts_calc
                if self.could_have_correct_imag_mode():
                    logger.info('Still have correct imaginary mode, trying '
                                'more  optimisation steps')

                    self.set_atoms(atoms=self.optts_calc.get_final_atoms())
                    self.optts_calc = Calculation(name=f'{self.name}_{name_ext}_reopt',
                                                  molecule=self,
                                                  method=method,
                                                  n_cores=Config.n_cores,
                                                  keywords=method.keywords.opt_ts,
                                                  bond_ids_to_add=self.bond_rearrangement.all,
                                                  other_input_block=method.keywords.optts_block)
                    self.optts_calc.run()
                else:
                    logger.info('Lost imaginary mode')
            else:
                logger.info('Optimisation did not converge')

        try:
            self.imaginary_frequencies = self.optts_calc.get_imaginary_freqs()
            self.set_atoms(atoms=self.optts_calc.get_final_atoms())
            self.energy = self.optts_calc.get_energy()

        except (AtomsNotFound, NoNormalModesFound):
            logger.error('Transition state optimisation calculation failed')

        return

    def _generate_conformers(self, n_confs=None):
        """Generate conformers at the TS """
        from autode.conformers.conformer import Conformer
        from autode.conformers.conf_gen import get_simanl_atoms
        from autode.conformers.conformers import conf_is_unique_rmsd

        n_confs = Config.num_conformers if n_confs is None else n_confs
        self.conformers = []

        distance_consts = get_distance_constraints(self)

        with Pool(processes=Config.n_cores) as pool:
            results = [pool.apply_async(get_simanl_atoms, (self, distance_consts, i)) for i in range(n_confs)]
            conf_atoms_list = [res.get(timeout=None) for res in results]

        for i, atoms in enumerate(conf_atoms_list):
            conf = Conformer(name=f'{self.name}_conf{i}', charge=self.charge,
                             mult=self.mult, atoms=atoms,
                             dist_consts=distance_consts)

            # If the conformer is unique on an RMSD threshold
            if conf_is_unique_rmsd(conf, self.conformers):
                conf.solvent = self.solvent
                conf.graph = deepcopy(self.graph)
                self.conformers.append(conf)

        logger.info(f'Generated {len(self.conformers)} conformer(s)')
        return None

    @requires_atoms()
    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 None

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

        # There is more than one imaginary frequency. Will assume that the most
        # negative is the correct mode..
        for disp_magnitude in [1, -1]:
            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.set_atoms(atoms)
            self.energy = energy
            self.imaginary_frequencies = self.optts_calc.get_imaginary_freqs()

        return None

    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

    def is_true_ts(self):
        """Is this TS a 'true' TS i.e. has at least on imaginary mode in the
        hessian and is the correct mode"""

        if self.energy is None:
            logger.warning('Cannot be true TS with no energy')
            return False

        if len(self.imaginary_frequencies) > 0:
            if self.has_correct_imag_mode(calc=self.optts_calc):
                logger.info('Found a transition state with the correct '
                            'imaginary mode & links reactants and products')
                return True

        return False

    def save_ts_template(self, folder_path=Config.ts_template_folder_path):
        """Save a transition state template containing the active bond lengths,
         solvent and charge in folder_path

        Keyword Arguments:
            folder_path (str): folder to save the TS template to
            (default: {None})
        """
        logger.info(f'Saving TS template for {self.name}')

        truncated_graph = get_truncated_active_mol_graph(self.graph,
                                                         active_bonds=self.bond_rearrangement.all)

        for bond in self.bond_rearrangement.all:
            truncated_graph.edges[bond]['distance'] = self.get_distance(*bond)

        ts_template = TStemplate(truncated_graph, solvent=self.solvent,
                                 charge=self.charge, mult=self.mult)
        ts_template.save_object(folder_path=folder_path)

        logger.info('Saved TS template')
        return None

    def __init__(self, ts_guess):
        """
        Transition State

        Arguments:
            ts_guess (autode.transition_states.ts_guess.TSguess)
        """
        super().__init__(atoms=ts_guess.atoms, reactant=ts_guess.reactant,
                         product=ts_guess.product, name=f'TS_{ts_guess.name}')

        self.bond_rearrangement = ts_guess.bond_rearrangement
        self.conformers = None

        self.optts_calc = None
        self.imaginary_frequencies = []

        self._update_graph()