示例#1
0
def test_thermal_cont_without_hess_run():

    calc = Calculation(name='test',
                       molecule=mol,
                       method=orca,
                       keywords=orca.keywords.hess,
                       temp=298)
    mol.energy = -1

    # Some blank output that exists
    calc.output.filename = 'test'
    calc.output.file_lines = []

    # Calculating the free energy contribution without a correct Hessian
    # calculation should not raise an exception(?)
    mol.calc_g_cont(calc=calc)
    assert mol.g_cont is None

    # and similarly with the enthalpic contribution
    mol.calc_h_cont(calc=calc)
    assert mol.h_cont is None
示例#2
0
def test_links_reacs_prods():

    tsguess.calc = Calculation(name=tsguess.name + '_hess',
                               molecule=tsguess,
                               method=method,
                               keywords=method.keywords.hess,
                               n_cores=Config.n_cores)
    # Should find the completed calculation output
    tsguess.calc.run()

    # Spoof an xtb install as reactant/product complex optimisation
    Config.lcode = 'xtb'
    # Config.XTB.path = here

    Config.num_complex_sphere_points = 4
    Config.num_complex_random_rotations = 1

    assert imag_mode_links_reactant_products(calc=tsguess.calc,
                                             reactant=reac_complex,
                                             product=product_complex,
                                             method=method)
示例#3
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
示例#4
0
def has_correct_mode(name, fbonds, bbonds):

    xyz_path = os.path.join(data_path, f'{name}.xyz')
    reac = Reactant(name='r', atoms=xyz_file_to_atoms(xyz_path))

    calc = Calculation(name=name,
                       molecule=reac,
                       method=orca,
                       keywords=orca.keywords.opt_ts,
                       n_cores=1)

    output_path = os.path.join(data_path, f'{name}.out')
    calc.output.filename = output_path
    calc.output.file_lines = open(output_path, 'r').readlines()

    bond_rearr = BondRearrangement(breaking_bonds=bbonds,
                                   forming_bonds=fbonds)

    # Don't require all bonds to be breaking/making in a 'could be ts' function
    return imag_mode_has_correct_displacement(calc, bond_rearr,
                                              delta_threshold=0.05,
                                              req_all=False)
示例#5
0
    def optimise(self,
                 method=None,
                 reset_graph=False,
                 calc=None,
                 keywords=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
            keywords (autode.wrappers.keywords.Keywords):

        Raises:
            (autode.exceptions.CalculationException):
        """
        logger.info(f'Running optimisation of {self.name}')

        if calc is None:
            assert method is not None
            keywords = method.keywords.opt if keywords is None else keywords

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

        calc.run()
        self.energy = calc.get_energy()
        self.atoms = calc.get_final_atoms()

        method_name = '' if method is None else method.name
        self.print_xyz_file(
            filename=f'{self.name}_optimised_{method_name}.xyz')

        if reset_graph:
            make_graph(self)

        return None
示例#6
0
    def optimise(self,
                 method=None,
                 reset_graph=False,
                 calc=None,
                 keywords=None):
        """
        Optimise the geometry of this conformer

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

        Keyword Arguments:
            reset_graph (bool):
            calc (autode.calculation.Calculation):
            keywords (autode.wrappers.keywords.Keywords):
        """
        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.atoms = opt.get_final_atoms()

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

        return None
示例#7
0
def test_orca_optts_calculation():

    methane = SolvatedMolecule(name='methane', smiles='C')
    methane.qm_solvent_atoms = []

    calc = Calculation(name='test_ts_reopt_optts',
                       molecule=methane,
                       method=method,
                       bond_ids_to_add=[(0, 1)],
                       keywords=opt_keywords,
                       other_input_block='%geom\n'
                       'Calc_Hess true\n'
                       'Recalc_Hess 40\n'
                       'Trust 0.2\n'
                       'MaxIter 100\nend')
    calc.run()

    assert os.path.exists('test_ts_reopt_optts_orca.inp')

    assert calc.get_normal_mode_displacements(mode_number=6) is not None
    assert calc.terminated_normally()
    assert calc.optimisation_converged()
    assert calc.optimisation_nearly_converged() is False
    assert len(calc.get_imaginary_freqs()) == 1

    # Gradients should be an n_atom x 3 array
    gradients = calc.get_gradients()
    assert len(gradients) == 5
    assert len(gradients[0]) == 3

    assert -599.437 < calc.get_enthalpy() < -599.436
    assert -599.469 < calc.get_free_energy() < -599.468
示例#8
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)
示例#9
0
def test_other_spin_states():

    o_singlet = Molecule(atoms=[Atom('O')], mult=1)
    o_singlet.name = 'molecule'

    calc = Calculation(name='O_singlet',
                       molecule=o_singlet,
                       method=method,
                       keywords=Config.MOPAC.keywords.sp)
    calc.run()
    singlet_energy = calc.get_energy()

    o_triplet = Molecule(atoms=[Atom('O')], mult=3)
    o_triplet.name = 'molecule'

    calc = Calculation(name='O_triplet',
                       molecule=o_triplet,
                       method=method,
                       keywords=Config.MOPAC.keywords.sp)
    calc.run()
    triplet_energy = calc.get_energy()

    assert triplet_energy < singlet_energy

    h_doublet = Molecule(atoms=[Atom('H')], mult=2)
    h_doublet.name = 'molecule'

    calc = Calculation(name='h',
                       molecule=h_doublet,
                       method=method,
                       keywords=Config.MOPAC.keywords.sp)
    calc.run()

    # Open shell doublet should work
    assert calc.get_energy() is not None

    h_quin = Molecule(atoms=[Atom('H')], mult=5)
    h_quin.name = 'molecule'

    with pytest.raises(UnsuppportedCalculationInput):
        calc = Calculation(name='h',
                           molecule=h_quin,
                           method=method,
                           keywords=Config.MOPAC.keywords.sp)
        calc.run()

    os.chdir(here)
示例#10
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)
示例#11
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)
示例#12
0
def test_input_gen():

    xtb = XTB()

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

    Config.keep_input_files = True
    calc.generate_input()
    assert os.path.exists('tmp_xtb.xyz')
    calc.clean_up()
    # Clean-up should do nothing if keep_input_files = True
    assert os.path.exists('tmp_xtb.xyz')

    # but should be able to be forced
    calc.clean_up(force=True)
    assert not os.path.exists('tmp_xtb.xyz')

    # Test the keywords parsing
    unsupported_func = Functional('PBE', orca='PBE')
    calc_kwds = Calculation(name='tmp',
                            molecule=test_mol,
                            method=xtb,
                            keywords=SinglePointKeywords([unsupported_func]))

    with pytest.raises(ex.UnsuppportedCalculationInput):
        calc_kwds.generate_input()
示例#13
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
示例#14
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
示例#15
0
def test_gauss_optts_calc():

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

    calc = Calculation(name='test_ts_reopt_optts',
                       molecule=test_mol,
                       method=method,
                       keywords=optts_keywords,
                       bond_ids_to_add=[(0, 1)])
    calc.run()
    print(calc.input.added_internals)
    assert os.path.exists('test_ts_reopt_optts_g09.com')

    bond_added = False
    for line in open('test_ts_reopt_optts_g09.com', 'r'):
        if 'B' in line and len(line.split()) == 3:
            bond_added = True
            assert line.split()[0] == 'B'
            assert line.split()[1] == '1'
            assert line.split()[2] == '2'

    assert bond_added

    assert calc.get_normal_mode_displacements(mode_number=6) is not None
    assert calc.terminated_normally()
    assert calc.optimisation_converged()
    assert calc.optimisation_nearly_converged() is False
    assert len(calc.get_imaginary_freqs()) == 1

    assert -40.324 < calc.get_free_energy() < -40.322
    assert -40.301 < calc.get_enthalpy() < -40.299

    os.remove('test_ts_reopt_optts_g09.com')
    os.chdir(here)
示例#16
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)
示例#17
0
def test_orca_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_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)
示例#18
0
文件: pes.py 项目: t-young31/autodE
def get_point_species(point,
                      species,
                      distance_constraints,
                      name,
                      method,
                      keywords,
                      n_cores,
                      energy_threshold=1):
    """
    On a 2d PES calculate the energy and the structure using a constrained
    optimisation

    Arguments:
        point (tuple(int)): Index of this point e.g. (0, 0) for the first point
                            on a 2D surface

        species (autode.species.Species):

        distance_constraints (dict): Keyed with atom indexes and the constraint
                             value as the value

        name (str):

        method (autode.wrappers.base.ElectronicStructureMethod):

        keywords (autode.wrappers.keywords.Keywords):

        n_cores (int): Number of cores to used for this calculation

    Keyword Arguments:
        energy_threshold (float): Above this energy (Hartrees) the calculation
                                  will be disregarded
    """
    logger.info(f'Calculating point {point} on PES surface')

    species.name = f'{name}_scan_{"-".join([str(p) for p in point])}'
    original_species = deepcopy(species)

    # Set up and run the calculation
    const_opt = Calculation(name=species.name,
                            molecule=species,
                            method=method,
                            n_cores=n_cores,
                            keywords=keywords,
                            distance_constraints=distance_constraints)
    try:
        species.optimise(method=method, calc=const_opt)

    except AtomsNotFound:
        logger.error(f'Optimisation failed for {point}')
        return original_species

    # If the energy difference is > 1 Hartree then likely something has gone
    # wrong with the EST method we need to be not on the first point to compute
    # an energy difference..
    if not all(p == 0 for p in point):
        if species.energy is None or np.abs(original_species.energy -
                                            species.energy) > energy_threshold:
            logger.error(f'PES point had a relative energy '
                         f'> {energy_threshold} Ha. Using the closest')
            return original_species

    return species
示例#19
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()
示例#20
0
# List of energies to be populated
energies = []

for r in rs:

    o_atom, h_atom = water.atoms[:2]
    curr_r = water.get_distance(0, 1)  # current O-H distance

    # Shift the hydrogen atom to the required distance
    # vector =  (h_atom.coord - o_atom.coord) / curr_r * (r - curr_r)
    vector = (h_atom.coord - o_atom.coord) * (r / curr_r - 1)
    h_atom.translate(vector)

    # Set up and run the calculation
    calc = Calculation(name=f'H2O_scan_{r:.2f}',
                       molecule=water,
                       method=xtb,
                       keywords=xtb.keywords.sp)
    calc.run()

    # Get the potential energy from the calculation
    energy = calc.get_energy()
    energies.append(energy)

# Plot the relative energy against the distance. 627.5 kcal mol-1 Ha-1
rel_energies = 627.5 * np.array([e - min(energies) for e in energies])

plt.plot(rs, rel_energies, marker='o')
plt.ylabel('ΔE / kcal mol$^{-1}$')
plt.xlabel('r / Å')
plt.savefig('OH_PES_unrelaxed.png')
示例#21
0
def test_solvation():

    methane = Molecule(name='solvated_methane',
                       smiles='C',
                       solvent_name='water')

    with pytest.raises(UnsuppportedCalculationInput):

        # Should raise on unsupported calculation type
        method.implicit_solvation_type = 'xxx'
        calc = Calculation(name='broken_solvation',
                           molecule=methane,
                           method=method,
                           keywords=sp_keywords)
        calc.run()

    method.implicit_solvation_type = 'CPCM'
    calc = Calculation(name='methane_cpcm',
                       molecule=methane,
                       method=method,
                       keywords=sp_keywords)
    calc.generate_input()

    assert any('cpcm' in line.lower()
               for line in open('methane_cpcm_orca.inp', 'r'))
    os.remove('methane_cpcm_orca.inp')

    method.implicit_solvation_type = 'SMD'
    calc = Calculation(name='methane_smd',
                       molecule=methane,
                       method=method,
                       keywords=sp_keywords)
    calc.generate_input()

    assert any('smd' in line.lower()
               for line in open('methane_smd_orca.inp', 'r'))
    os.remove('methane_smd_orca.inp')
示例#22
0
def test_fix_unique():
    """So calculations with different input but the same name are not skipped
    autodE checks the input of each previously run calc with the name name"""

    orca = ORCA()

    calc = Calculation(name='tmp',
                       molecule=test_mol,
                       method=orca,
                       keywords=orca.keywords.sp)
    calc._fix_unique()
    assert calc.name == 'tmp_orca'

    # Should generate a register
    assert os.path.exists('.autode_calculations')
    assert len(open('.autode_calculations', 'r').readlines()) == 1

    calc = Calculation(name='tmp',
                       molecule=test_mol,
                       method=orca,
                       keywords=orca.keywords.opt)
    calc._fix_unique()
    assert calc.name != 'tmp_orca'
    assert calc.name == 'tmp_orca0'

    # no need to fix unique if the name is different
    calc = Calculation(name='tmp2',
                       molecule=test_mol,
                       method=orca,
                       keywords=orca.keywords.opt)
    calc._fix_unique()
    assert calc.name == 'tmp2_orca'
示例#23
0
def test_gradients():

    h2 = Molecule(name='h2', atoms=[Atom('H'), Atom('H', x=1.0)])
    calc = Calculation(name='h2_grad',
                       molecule=h2,
                       method=method,
                       keywords=method.keywords.grad)
    calc.run()
    h2.energy = calc.get_energy()

    delta_r = 1E-8

    # Energy of a finite difference approximation
    h2_disp = Molecule(name='h2_disp',
                       atoms=[Atom('H'), Atom('H', x=1.0 + delta_r)])
    calc = Calculation(name='h2_disp',
                       molecule=h2_disp,
                       method=method,
                       keywords=method.keywords.grad)
    calc.run()
    h2_disp.energy = calc.get_energy()

    delta_energy = h2_disp.energy - h2.energy  # Ha
    grad = delta_energy / delta_r  # Ha A^-1

    calc = Calculation(name='h2_grad',
                       molecule=h2,
                       method=method,
                       keywords=method.keywords.grad)

    calc.run()

    diff = calc.get_gradients()[1, 0] - grad  # Ha A^-1

    # Difference between the absolute and finite difference approximation
    assert np.abs(diff) < 1E-3
示例#24
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)
示例#25
0
# and constrained optimisations (relaxed) calculations
sp_energies, opt_energies = [], []

for r in rs:

    o_atom, h_atom = water.atoms[:2]
    curr_r = water.get_distance(0, 1)  # current O-H distance

    # Shift the hydrogen atom to the required distance
    # vector =  (h_atom.coord - o_atom.coord) / curr_r * (r - curr_r)
    vector = (h_atom.coord - o_atom.coord) * (r / curr_r - 1)
    h_atom.translate(vector)

    # Set up and run the single point energy evaluation
    sp = Calculation(name=f'H2O_scan_unrelaxed_{r:.2f}',
                     molecule=water,
                     method=xtb,
                     keywords=xtb.keywords.sp)
    sp.run()
    sp_energies.append(sp.get_energy())

    # Set up the constrained optimisation calculation where the distance
    # constraints are given as a dictionary keyed with a tuple of atom indexes
    # with the distance as the value
    opt = Calculation(name=f'H2O_scan_relaxed_{r:.2f}',
                      molecule=water,
                      method=xtb,
                      keywords=xtb.keywords.low_opt,
                      distance_constraints={(0, 1): r})
    opt.run()
    opt_energies.append(opt.get_energy())
示例#26
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()
示例#27
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)
示例#28
0
    # Create arrays for OH distances and their energies
    rs = np.linspace(0.65, 2.0, num=15)
    energies = []

    # Calculate the energy array
    for r in rs:

        o_atom, h_atom = water.atoms[:2]
        curr_r = water.distance(0, 1)

        vector = (h_atom.coord - o_atom.coord) * (r / curr_r - 1)
        h_atom.translate(vector)

        # Set up and run the calculation
        calc = Calculation(name=f'H2O_scan_{r:.2f}',
                           molecule=water,
                           method=orca,
                           keywords=keywords)
        calc.run()

        # Get the potential energy from the calculation
        energy = calc.get_energy()
        energies.append(energy)

    # Plot the relative energy against the distance. 627.5 kcal mol-1 Ha-1
    rel_energies = 627.5 * np.array([e - min(energies) for e in energies])
    plt.plot(rs, rel_energies, marker='o', label=dft_name)

# Add labels to the plot and save the figure
plt.ylabel('ΔE / kcal mol$^{-1}$')
plt.xlabel('r / Å')
plt.legend()
示例#29
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()
示例#30
0
文件: base.py 项目: t-young31/autodE
class TSbase(Species):
    def _init_graph(self):
        """Set the molecular graph for this TS object from the reactant"""
        logger.warning(f'Setting the graph of {self.name} from reactants')

        self.graph = self.reactant.graph.copy()
        return None

    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):
        """
        # 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

    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

    def __init__(self, atoms, reactant, product, name='ts_guess'):

        super().__init__(name=name,
                         atoms=atoms,
                         charge=reactant.charge,
                         mult=reactant.mult)
        self.solvent = reactant.solvent
        self.atoms = atoms

        self.reactant = reactant
        self.product = product

        self.calc = None
        self.bond_rearrangement = None

        self._init_graph()