예제 #1
0
    def _minimise(self, method, n_cores, etol, max_n=30):
        """Minimise th energy of every image in the NEB"""
        logger.info(f'Minimising to ∆E < {etol:.4f} Ha on all NEB coordinates')

        result = minimize(total_energy,
                          x0=self.images.coords(),
                          method='L-BFGS-B',
                          jac=derivative,
                          args=(self.images, method, n_cores),
                          tol=etol,
                          options={'maxfun': max_n})

        logger.info(f'NEB path energy = {result.fun:.5f} Ha, {result.message}')
        return result
예제 #2
0
    def _check_solvent(self):
        """Check that all the solvents are the same for reactants and products
        """
        molecules = self.reacs + self.prods

        if self.solvent is None:
            if all([mol.solvent is None for mol in molecules]):
                logger.info('Reaction is in the gas phase')
                return

            elif all([mol.solvent is not None for mol in molecules]):
                if not all([mol.solvent == self.reacs[0].solvent for mol in molecules]):
                    logger.critical('Solvents in reactants and products '
                                    'don\'t match')
                    raise SolventsDontMatch

                else:
                    logger.info(f'Setting the reaction solvent to '
                                f'{self.reacs[0].solvent}')
                    self.solvent = self.reacs[0].solvent

            else:
                logger.critical('Some species solvated and some not!')
                raise SolventsDontMatch

        if self.solvent is not None:
            logger.info(f'Setting solvent to {self.solvent.name} for all '
                        f'molecules in the reaction')

            for mol in self.reacs + self.prods:
                mol.solvent = self.solvent

        logger.info(f'Set the solvent of all species in the reaction to '
                    f'{self.solvent.name}')
        return None
예제 #3
0
def get_atoms_rotated_stereocentres(species, atoms, rand):
    """If two stereocentres are bonded, rotate them randomly with respect
    to each other

    Arguments:
        species (autode.species.Species):
        atoms (list(autode.atoms.Atom)):
        rand (np.RandomState): random state

    Returns:
        (list(autode.atoms.Atom)): Atoms
    """

    stereocentres = [
        node for node in species.graph.nodes
        if species.graph.nodes[node]['stereo'] is True
    ]

    # Check on every pair of stereocenters
    for (i, j) in combinations(stereocentres, 2):
        if (i, j) not in species.graph.edges:
            continue

        # Don't rotate if the bond connecting the centers is a π-bond
        if species.graph.edges[i, j]['pi'] is True:
            logger.info('Stereocenters were π bonded – not rotating')
            continue

        try:
            left_idxs, right_idxs = split_mol_across_bond(species.graph,
                                                          bond=(i, j))

        except ex.CannotSplitAcrossBond:
            logger.warning('Splitting across this bond does not give two '
                           'components - could have a ring')
            return atoms

        # Rotate the left hand side randomly
        rot_axis = atoms[i].coord - atoms[j].coord
        theta = 2 * np.pi * rand.rand()
        idxs_to_rotate = left_idxs if i in left_idxs else right_idxs

        # Rotate all the atoms to the left of this bond, missing out i as that
        # is the origin for rotation and thus won't move
        for n in idxs_to_rotate:
            if n == i:
                continue
            atoms[n].rotate(axis=rot_axis, theta=theta, origin=atoms[i].coord)

    return atoms
예제 #4
0
    def run(self):
        """Run the calculation using the EST method """
        logger.info(f'Running calculation {self.name}')

        # Set an input filename and generate the input
        self.generate_input()

        # Set the output filename, run the calculation and clean up the files
        self.output.filename = self.method.get_output_filename(self)
        self.execute_calculation()
        self.clean_up()
        self._add_to_comp_methods()

        return None
예제 #5
0
    def calculation_terminated_normally(self, calc):

        for n_line, line in enumerate(reversed(calc.output.file_lines)):
            if any(substring in line for substring in['CITATION',
                                                      'Failed to converge in maximum number of steps or available time']):
                logger.info('nwchem terminated normally')
                return True
            if 'MPI_ABORT' in line:
                return False

            if n_line > 500:
                return False

        return False
예제 #6
0
def get_fbonds_bbonds_1b(reac, prod, possible_brs, all_possible_bbonds,
                         all_possible_fbonds, possible_bbond_and_fbonds,
                         bbond_atom_type_fbonds, fbond_atom_type_bbonds):
    logger.info('Getting possible 1 breaking bond rearrangements')

    for bbond in all_possible_bbonds[0]:
        # Break one bond
        possible_brs = add_bond_rearrangment(possible_brs,
                                             reac,
                                             prod,
                                             fbonds=[],
                                             bbonds=[bbond])

    return possible_brs
예제 #7
0
파일: ORCA.py 프로젝트: jmgx30/autodE
    def get_imaginary_freqs(self, calc):
        imag_freqs = []

        for i, line in enumerate(calc.output.file_lines):
            if 'VIBRATIONAL FREQUENCIES' in line:
                last_line = i + 3 * calc.molecule.n_atoms + 5

                # Reset every time freqs are found, so the final is returned
                freq_lines = calc.output.file_lines[i + 5:last_line]
                freqs = [float(l.split()[1]) for l in freq_lines]
                imag_freqs = [freq for freq in freqs if freq < 0]

        logger.info(f'Found imaginary freqs {imag_freqs}')
        return imag_freqs
예제 #8
0
def get_truncated_active_mol_graph(graph, active_bonds=None):
    """
    Generate a truncated graph of a graph that only contains the active bond
    atoms and their nearest neighbours

    Arguments:
        graph (nx.Graph):
        active_bonds (list(tuple(int)):
    """

    if active_bonds is None:
        # Molecular graph may already define the active edges
        active_bonds = [
            pair for pair in graph.edges if graph.edges[pair]['active']
        ]

    if len(active_bonds) == 0:
        raise ValueError('Could not generate truncated active molecular '
                         'graph with no active bonds')

    t_graph = nx.Graph()

    # Add all nodes that connect active bonds
    for bond in active_bonds:

        for idx in bond:
            if idx not in t_graph.nodes:

                label = graph.nodes[idx]['atom_label']
                t_graph.add_node(idx, atom_label=label)

        t_graph.add_edge(*bond, active=True, pi=False)

    # For every active atom add the nearest neighbours
    for idx in deepcopy(t_graph.nodes):
        neighbours = graph.neighbors(idx)

        # Add nodes and edges for all atoms and bonds to the neighbours that
        # don't already exist in the graph
        for n_atom_index in neighbours:
            if n_atom_index not in t_graph.nodes:
                label = graph.nodes[idx]['atom_label']
                t_graph.add_node(n_atom_index, atom_label=label)

            if (idx, n_atom_index) not in t_graph.edges:
                t_graph.add_edge(idx, n_atom_index, pi=False, active=False)

    logger.info(f'Truncated graph generated. {t_graph.number_of_nodes()} '
                f'nodes and {t_graph.number_of_edges()} edges')
    return t_graph
예제 #9
0
파일: species.py 프로젝트: dpregeljc/autodE
    def single_point(self, method):
        """Calculate the single point energy of the species with a
        autode.wrappers.base.ElectronicStructureMethod"""
        logger.info(f'Running single point energy evaluation of {self.name}')

        sp = Calculation(name=f'{self.name}_sp',
                         molecule=self,
                         method=method,
                         keywords=method.keywords.sp,
                         n_cores=Config.n_cores)
        sp.run()
        self.energy = sp.get_energy()

        return None
예제 #10
0
    def _init_smiles(self, smiles):
        """Initialise a molecule from a SMILES string using RDKit if it's
        purely organic"""

        if any(metal in smiles for metal in metals):
            init_smiles(self, smiles)

        else:
            init_organic_smiles(self, smiles)

        logger.info(f'Initialisation with SMILES successful. '
                    f'Charge={self.charge}, Multiplicity={self.mult}, '
                    f'Num. Atoms={self.n_atoms}')
        return None
예제 #11
0
def get_template_ts_guess(reactant, product, bond_rearrangement, name, method, dist_thresh=4.0):
    """Get a transition state guess object by searching though the stored TS
    templates

    Arguments:
        reactant (autode.complex.ReactantComplex):
        bond_rearrangement (autode.bond_rearrangement.BondRearrangement):
        product (autode.complex.ProductComplex):
        method (autode.wrappers.base.ElectronicStructureMethod):
        name (str):
        keywords (list(str)): Keywords to use for the ElectronicStructureMethod

    Keyword Arguments:
        dist_thresh (float): distance above which a constrained optimisation
                             probably won't work due to the initial geometry
                             being too far away from the ideal (default: {4.0})

    Returns:
        TSGuess object: ts guess object
    """
    logger.info('Getting TS guess from stored TS template')
    active_bonds_and_dists_ts = {}

    # This will add edges so don't modify in place
    mol_graph = get_truncated_active_mol_graph(graph=reactant.graph,
                                               active_bonds=bond_rearrangement.all)
    ts_guess_templates = get_ts_templates()

    for ts_template in ts_guess_templates:

        if not template_matches(reactant=reactant, ts_template=ts_template,
                                truncated_graph=mol_graph):
            continue

        # Get the mapping from the matching template
        mapping = get_mapping_ts_template(larger_graph=mol_graph,
                                          smaller_graph=ts_template.graph)

        for active_bond in bond_rearrangement.all:
            i, j = active_bond
            try:
                active_bonds_and_dists_ts[active_bond] = ts_template.graph.edges[mapping[i],
                                                                                 mapping[j]]['distance']
            except KeyError:
                logger.warning(f'Couldn\'t find a mapping for bond {i}-{j}')

        if len(active_bonds_and_dists_ts) == len(bond_rearrangement.all):
            logger.info('Found a TS guess from a template')

            if any([reactant.get_distance(*bond) > dist_thresh for bond in bond_rearrangement.all]):
                logger.info(f'TS template has => 1 active bond distance larger than {dist_thresh}. Passing')
                pass

            else:
                return get_ts_guess_constrained_opt(reactant, method=method, keywords=method.keywords.opt, name=name,
                                                    distance_consts=active_bonds_and_dists_ts, product=product)

    logger.info('Could not find a TS guess from a template')
    return None
예제 #12
0
    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
예제 #13
0
    def get_imaginary_freqs(self, calc):
        """frequencies() needs to be used in the psi4 input file."""
        imaginary_frequencies = []

        for line in calc.output.file_lines:
            if 'post-proj' in line and 'i' in line and '[cm^-1]' in line:
                imaginary_frequencies.append(line.split()[3])

        if len(imaginary_frequencies) > 0:
            logger.info(f'Found imaginary frequencies {imaginary_frequencies}')
            return imaginary_frequencies

        else:
            logger.info('Could not find any imaginary frequencies.')
            return None
예제 #14
0
파일: ci.py 프로젝트: gabegomes/autodE
    def increment(self):
        """Increment the counter, and switch on a climbing image"""
        super().increment()

        if self[0].iteration < self.wait_iteration:
            # No need to do anything else
            return

        if self.peak_idx is None:
            logger.error('Lost NEB peak - cannot switch on CI')
            return

        logger.info(f'Setting image {self.peak_idx} as the CI')
        self[self.peak_idx] = CImage(image=self[self.peak_idx])
        return None
예제 #15
0
    def products_made(self):
        """Check that somewhere on the surface the molecular graph is
        isomorphic to the product"""
        logger.info('Checking product(s) are made somewhere on the surface')

        for i in range(self.n_points_r1):
            for j in range(self.n_points_r2):
                make_graph(self.species[i, j])

                if is_isomorphic(graph1=self.species[i, j].graph,
                                 graph2=self.product_graph):
                    logger.info(f'Products made at ({i}, {j})')
                    return True

        return False
예제 #16
0
def get_mapping(graph, other_graph):
    """Return a sorted mapping"""

    logger.info('Running isomorphism')
    gm = isomorphism.GraphMatcher(
        graph,
        other_graph,
        node_match=isomorphism.categorical_node_match('atom_label', 'C'))

    try:
        mapping = next(gm.match())
    except StopIteration:
        raise ex.NoMapping

    return {i: mapping[i] for i in sorted(mapping)}
예제 #17
0
    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
예제 #18
0
def get_unique_confs(conformers, energy_threshold_kj=1):
    """
    For a list of conformers return those that are unique based on an energy
    threshold in kJ mol^-1

    Arguments:
        conformers (list(autode.conformer.Conformer)):
        energy_threshold_kj (float): Energy threshold in kJ mol-1

    Returns:
        (list(autode.conformers.conformers.Conformer)): List of conformers
    """
    logger.info(f'Stripping conformers with energy ∆E < {energy_threshold_kj} '
                f'kJ mol-1 to others')

    n_conformers = len(conformers)

    # Conformer.energy is in Hartrees
    threshold = energy_threshold_kj / Constants.ha2kJmol

    # The first conformer must be unique, if it has an energy
    unique_conformers = []

    for conformer in conformers:

        if conformer.energy is None:
            logger.error('Conformer had no energy. Excluding')
            continue

        # Iterate through all the unique conformers already found and check
        # that the energy is not similar
        unique = True
        for other_conformer in unique_conformers:
            if np.abs(conformer.energy - other_conformer.energy) < threshold:
                unique = False
                break

        if unique:
            unique_conformers.append(conformer)

    n_unique_conformers = len(unique_conformers)
    logger.info(f'Stripped {n_conformers - n_unique_conformers} conformer(s) '
                f'from a total of {n_conformers}')

    if n_unique_conformers == 0:
        logger.error('Have no conformers!')

    return unique_conformers
예제 #19
0
def get_fbonds_bbonds_2b1f(reac, prod, possible_brs, all_possible_bbonds, all_possible_fbonds, possible_bbond_and_fbonds, bbond_atom_type_fbonds, fbond_atom_type_bbonds):
    logger.info('Getting possible 2 breaking and 1 forming bond rearrangements')

    if len(all_possible_bbonds) == 2 and len(all_possible_fbonds) == 1:
        # Make a bond and break two bonds, all of different types
        possibles = itertools.product(all_possible_fbonds[0],
                                      all_possible_bbonds[0],
                                      all_possible_bbonds[1])

        for fbond, bbond1, bbond2 in possibles:
            possible_brs = add_bond_rearrangment(possible_brs, reac, prod,
                                                 fbonds=[fbond],
                                                 bbonds=[bbond1, bbond2])

    elif len(all_possible_bbonds) == 1 and len(all_possible_fbonds) == 1:
        # Make a bond of one type, break two bonds of another type
        two_same_possibles = itertools.combinations(all_possible_bbonds[0], 2)
        possibles = itertools.product(all_possible_fbonds[0],
                                      two_same_possibles)

        for fbond, (bbond1, bbond2) in possibles:
            possible_brs = add_bond_rearrangment(possible_brs, reac, prod,
                                                 fbonds=[fbond],
                                                 bbonds=[bbond1, bbond2])

    elif len(all_possible_bbonds) == 1 and len(all_possible_fbonds) == 0:
        for bbonds, fbonds in possible_bbond_and_fbonds:
            # Make and break a bond of one type, break a bond of a different
            # type
            possibles = itertools.product(fbonds, all_possible_bbonds[0],
                                          bbonds)

            for fbond, bbond1, bbond2 in possibles:
                possible_brs = add_bond_rearrangment(possible_brs, reac, prod,
                                                     fbonds=[fbond],
                                                     bbonds=[bbond1, bbond2])

        # Make and break two bonds, all of the same type
        two_same_possibles = itertools.combinations(all_possible_bbonds[0], 2)
        possibles = itertools.product(bbond_atom_type_fbonds,
                                      two_same_possibles)

        for fbond, (bbond1, bbond2) in possibles:
            possible_brs = add_bond_rearrangment(possible_brs, reac, prod,
                                                 fbonds=[fbond],
                                                 bbonds=[bbond1, bbond2])

    return possible_brs
예제 #20
0
    def calculate_reaction_profile(self, units=KcalMol):
        """Calculate a multistep reaction profile using the products of step 1
        as the reactants of step 2 etc."""
        logger.info('Calculating reaction profile')
        h_method = get_hmethod()

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

            for mol in reaction.reacs + reaction.prods:

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

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

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

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

            return None

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

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

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

        plot_reaction_profile(self.reactions, units=units, name=self.name)
        return None
예제 #21
0
def plot_reaction_profile(reactions, units, name, free_energy=False,
                          enthalpy=False):
    """For a set of reactions plot the reaction profile using matplotlib

    Arguments:
        reactions (list((autode.reaction.Reaction)):
        units (autode.units.Units):
        name (str):

    Keyword Arguments:
        free_energy (bool): Plot the free energy profile (G)
        enthalpy (bool): Plot the enthalpic profile (H)
    """
    logger.info('Plotting reaction profile')

    if free_energy and enthalpy:
        raise AssertionError('Cannot plot a profile in both G and H')

    fig, ax = plt.subplots()

    # Get the energies for the reaction profile (y values) plotted against the
    # reaction coordinate (zi_s)
    energies = calculate_reaction_profile_energies(reactions,
                                                   units=units,
                                                   free_energy=free_energy,
                                                   enthalpy=enthalpy)
    zi_s = np.array(range(len(energies)))

    try:
        plot_smooth_profile(zi_s, energies, ax=ax)

    except CouldNotPlotSmoothProfile:
        plot_points(zi_s, energies, ax=ax)

    ec = 'E'
    if free_energy:
        ec = 'G'
    elif enthalpy:
        ec = 'H'

    plt.ylabel(f'∆${ec}$ / {units.name}', fontsize=12)
    plt.ylim(min(energies)-3, max(energies)+3)
    plt.xticks([])
    plt.subplots_adjust(top=0.95, right=0.95)
    fig.text(.1, .05, get_reaction_profile_warnings(reactions), ha='left',
             fontsize=6, wrap=True)

    return save_plot(plt, filename=f'{name}_reaction_profile.png')
예제 #22
0
def add_remaining_atoms(truncated_graph, full_graph, s_molecule):
    """Truncation can lead to a split across a C-C bond in a ring where one
    of the carbons is no longer has 4 nearest neighbours"""

    for i in deepcopy(truncated_graph.nodes):

        # No modification needed if the valency of this atom is retained
        n_truncated_neighbours = len(list(truncated_graph.neighbors(i)))
        n_full_neighbours = len(list(full_graph.neighbors(i)))

        if n_truncated_neighbours == n_full_neighbours:
            continue

        # Only consider non-swapped atoms e.g. not where C -> H
        if truncated_graph.nodes[i]['atom_label'] != full_graph.nodes[i][
                'atom_label']:
            continue

        logger.warning(f'Atom {i} changed valency in truncation')
        for n in nx.neighbors(full_graph, i):

            if (i, n) in truncated_graph.edges:
                continue

            # Missing atom n from the truncated graph - probably truncated
            # X -> H but was also bonded to another atom also in the truncated
            # graph.
            x, y, z = s_molecule.atoms[n].coord
            s_molecule.atoms.append(Atom(atomic_symbol='Og', x=x, y=y, z=z))

            # Add the capping H atom in place of the X atom just added
            # will be the last atom index, if it's just been added
            add_capping_atom(atom_index=i,
                             n_atom_index=len(s_molecule.atoms) - 1,
                             graph=truncated_graph,
                             s_molecule=s_molecule)

            # Also add the edge between the added atom and the one that changed
            # valency
            truncated_graph.add_edge(i,
                                     len(s_molecule.atoms) - 1,
                                     pi=False,
                                     active=False)

        logger.info(
            f'New valency is {len(list(truncated_graph.neighbors(i)))}')

    return None
예제 #23
0
파일: complex.py 프로젝트: t-young31/autodE
    def _generate_conformers(self):
        """
        Generate rigid body conformers of a complex by (1) Fixing the first m
        olecule, (2) initialising the second molecule's COM evenly on the points
        of a sphere around the first with a random rotation and (3) iterating
        until all molecules in the complex have been added
        """
        if len(self.molecules) < 2:
            # Single (or zero) molecule complex only has a single *rigid body*
            # conformer
            self.conformers = [get_conformer(name=self.name, species=self)]

            return None

        n_molecules = len(self.molecules)  # Number of molecules in the complex
        self.conformers = []
        n = 0  # Current conformer number

        points_on_sphere = get_points_on_sphere(
            n_points=Config.num_complex_sphere_points)

        for _ in iterprod(range(Config.num_complex_random_rotations),
                          repeat=n_molecules - 1):
            # Generate the rotation thetas and axes
            rotations = [
                np.random.uniform(-np.pi, np.pi, size=4)
                for _ in range(n_molecules - 1)
            ]

            for points in iterprod(points_on_sphere, repeat=n_molecules - 1):

                conformer = get_conformer(species=self,
                                          name=f'{self.name}_conf{n}')
                atoms = get_complex_conformer_atoms(self.molecules, rotations,
                                                    points)
                conformer.set_atoms(atoms)

                self.conformers.append(conformer)
                n += 1

                if n == Config.max_num_complex_conformers:
                    logger.warning(
                        f'Generated the maximum number of complex conformers ({n})'
                    )
                    return None

        logger.info(f'Generated {n} conformers')
        return None
예제 #24
0
    def set_lines(self):
        """
        Set the output files lines. This may be slow for large files but should
        not become a bottleneck when running standard DFT/WF calculations

        Returns:
            (None)
        """
        logger.info('Setting output file lines')

        if not os.path.exists(self.filename):
            raise ex.NoCalculationOutput

        self.file_lines = open(self.filename, 'r', encoding="utf-8").readlines()

        return None
예제 #25
0
    def get_gradients(self):
        """
        Get the gradient (dE/dr) with respect to atomic displacement from a
        calculation

        Returns:
            (np.ndarray): Gradient vectors for each atom (Ha Å^-1)
                          gradients.shape = (n_atoms, 3)
        """
        logger.info(f'Getting gradients from {self.output.filename}')
        gradients = self.method.get_gradients(self)

        if len(gradients) != self.molecule.n_atoms:
            raise ex.CouldNotGetProperty(name='gradients')

        return gradients
예제 #26
0
    def _init_tensors(self, reactant, r1s, r2s):
        """Initialise the matrices of Species and distances"""
        logger.info(f'Initialising the {len(r1s)}x{len(r2s)} PES matrices')

        assert self.rs.shape == self.species.shape

        for i in range(len(self.rs)):
            for j in range(len(self.rs[i])):
                # Tuple of distances
                self.rs[i, j] = (r1s[i], r2s[j])

        # Copy of the reactant complex, whose atoms/energy will be set in the
        # scan
        self.species[0, 0] = deepcopy(reactant)

        return None
예제 #27
0
파일: ci.py 프로젝트: gabegomes/autodE
    def _minimise(self, method, n_cores, etol, max_n=30):
        """Minimise th energy of every image in the NEB"""
        logger.info(f'Minimising to ∆E < {etol:.4f} Ha on all NEB coordinates')
        result = super()._minimise(method, n_cores, etol, max_n)

        if any(im.iteration > self.images.wait_iteration
               for im in self.images):
            return result

        logger.info('Converged before CI was turned on. Reducing the wait and '
                    'minimising again')

        self.images.wait_iteration = max(im.iteration for im in self.images)
        result = super()._minimise(method, n_cores, etol, max_n)

        return result
예제 #28
0
    def calc_delta_e(self):
        """Calculate the ∆Er of a reaction defined as
        ∆E = E(products) - E(reactants)

        Returns:
            (float): Energy difference in Hartrees
        """
        logger.info('Calculating ∆Er')

        if any(mol.energy is None for mol in self.reacs + self.prods):
            logger.error('Cannot calculate ∆Er. At least one required energy '
                         'was None')
            return None

        return (sum([p.energy for p in self.prods]) -
                sum([r.energy for r in self.reacs]))
예제 #29
0
    def get_atomic_charges(self):
        """
        Get the partial atomic charges from a calculation. The method used to
        calculate them depends on the QM method and are implemented in their
        respective wrappers

        Returns:
            (list(float)): Atomic charges in units of e
        """
        logger.info(f'Getting atomic charges from {self.output.filename}')
        charges = self.method.get_atomic_charges(self)

        if len(charges) != self.molecule.n_atoms:
            raise ex.CouldNotGetProperty(name='atomic charges')

        return charges
예제 #30
0
    def _generate_conformers(self, n_confs=None):
        """
        Use a simulated annealing approach to generate conformers for this
        molecule.

        Keyword Arguments:
            n_confs (int): Number of conformers requested if None default to
            autode.Config.num_conformers
        """

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

        if self.smiles is not None and self.rdkit_conf_gen_is_fine:
            logger.info(f'Using RDKit to gen conformers. {n_confs} requested')

            method = AllChem.ETKDGv2()
            method.pruneRmsThresh = Config.rmsd_threshold
            method.numThreads = Config.n_cores

            logger.info('Running conformation generation with RDKit... running')
            conf_ids = list(AllChem.EmbedMultipleConfs(self.rdkit_mol_obj,
                                                       numConfs=n_confs,
                                                       params=method))
            logger.info('                                          ... done')

            conf_atoms_list = [get_atoms_from_rdkit_mol_object(self.rdkit_mol_obj, conf_id) for conf_id in conf_ids]

        else:
            logger.info('Using simulated annealing to generate conformers')
            with Pool(processes=Config.n_cores) as pool:
                results = [pool.apply_async(get_simanl_atoms, (self, None, 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)

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

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