Exemple #1
0
def imag_mode_generates_other_bonds(ts, f_species, b_species,
                                    bond_rearrangement):
    """Determine if the forward or backwards displaced molecule break or make
    bonds that aren't in all the active bonds bond_rearrangement.all. Will be
    fairly conservative here"""

    for species in (ts, f_species, b_species):
        make_graph(species, rel_tolerance=0.3)

    for product in (f_species, b_species):

        new_bonds_in_product = set([
            bond for bond in product.graph.edges if bond not in ts.graph.edges
        ])

        # If there are new bonds in the forward displaced species that are not
        # part of the bond rearrangement
        if any(bond not in bond_rearrangement.all
               for bond in new_bonds_in_product):
            logger.warning(f'New bonds in product: {new_bonds_in_product}')
            logger.warning(f'Bond rearrangement: {bond_rearrangement.all}')
            return True

    logger.info('Imaginary mode does not generate any other unwanted bonds')
    return False
Exemple #2
0
def calc_multiplicity(molecule, n_radical_electrons):
    """Calculate the spin multiplicity 2S + 1 where S is the number of
    unpaired electrons

    Arguments:
        molecule (autode.molecule.Molecule):
        n_radical_electrons (int):
    Returns:
        int: multiplicity of the molecule
    """

    if molecule.mult == 1 and n_radical_electrons == 0:
        return 1

    if molecule.mult == 1 and n_radical_electrons == 1:
        # Cannot have multiplicity = 1 and 1 radical electrons – override
        # default multiplicity
        return 2

    if molecule.mult == 1 and n_radical_electrons > 1:
        logger.warning('Diradicals by default singlets. Set mol.mult if it\'s '
                       'any different')
        return 1

    return molecule.mult
Exemple #3
0
    def get_free_energy(self, calc):
        """Get the Gibbs free energy (G) from an ORCA calculation output"""

        if calc.molecule.n_atoms == 1:
            logger.warning('ORCA fails to calculate the entropy for a single '
                           'atom, returning the correct G in 1 atm')
            h = self.get_enthalpy(calc)
            s = calc_atom_entropy(atom_label=calc.molecule.atoms[0].label,
                                  temp=calc.input.temp)  # J K-1 mol-1

            # Calculate H - TS, the latter term from Jmol-1 -> Ha
            return h - s * calc.input.temp

        for line in reversed(calc.output.file_lines):
            if ('Final Gibbs free energy' in line
                    or 'Final Gibbs free enthalpy' in line):

                try:
                    return float(line.split()[-2])

                except ValueError:
                    break

        logger.error('Could not get the free energy from the calculation. '
                     'Was a frequency requested?')
        return None
Exemple #4
0
def are_coords_reasonable(coords):
    """
    Determine if a set of coords are reasonable. No distances can be < 0.7 Å
    and if there are more than 4 atoms ensure they do not all lie in the same
    plane. The latter possibility arises from RDKit's conformer generation
    algorithm
    breaking
    Arguments:
        coords (np.ndarray): Species coordinates as a n_atoms x 3 array
    Returns:
        bool:
    """

    n_atoms = len(coords)

    # Generate a n_atoms x n_atoms matrix with ones on the diagonal
    dist_mat = distance_matrix(coords, coords) + np.identity(n_atoms)

    if np.min(dist_mat) < 0.7:
        logger.warning('There is a distance < 0.7 Å. Structure is *not* '
                       'sensible')
        return False

    if n_atoms > 4:
        if all([coord[2] == 0.0 for coord in coords]):
            logger.warning('RDKit likely generated a wrong geometry. Structure'
                           ' is *not* sensible')
            return False

    return True
Exemple #5
0
    def _set_lowest_energy_conformer(self):
        """Set the species energy and atoms as those of the lowest energy
        conformer"""
        lowest_energy = None

        for conformer in self.conformers:
            if conformer.energy is None:
                continue

            # Conformers don't have a molecular graph, so make it
            make_graph(conformer)

            if not is_isomorphic(conformer.graph, self.graph,
                                 ignore_active_bonds=True):
                logger.warning('Conformer had a different graph. Ignoring')
                continue

            # If the conformer retains the same connectivity, up the the active
            # atoms in the species graph

            if lowest_energy is None:
                lowest_energy = conformer.energy

            if conformer.energy <= lowest_energy:
                self.energy = conformer.energy
                self.set_atoms(atoms=conformer.atoms)
                lowest_energy = conformer.energy

        return None
Exemple #6
0
def modify_keywords_for_point_charges(keywords):
    """For a list of Gaussian keywords modify to include z-matrix if not
    already included. Required if point charges are included in the calc"""
    logger.warning('Modifying keywords as point charges are present')

    keywords.append('Charge')

    for keyword in keywords:
        if 'opt' not in keyword.lower():
            continue

        opt_options = []
        if '=(' in keyword:
            # get the individual options
            unformated_options = keyword[5:-1].split(',')
            opt_options = [
                option.lower().strip() for option in unformated_options
            ]

        elif '=' in keyword:
            opt_options = [keyword[4:]]

        if not any(option.lower() == 'z-matrix' for option in opt_options):
            opt_options.append('Z-Matrix')

        new_keyword = f'Opt=({", ".join(opt_options)})'
        keywords.remove(keyword)
        keywords.append(new_keyword)

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

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

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

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

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

        logger.warning('Species does *not* have the correct imaginary mode')
        return False
Exemple #8
0
def contains_peak(species_list):
    """
    Does this list of species contain a peak in the energy?

    Arguments:
        species_list (list(autode.species.Species):

    Returns:
        (bool):
    """
    if any(species.energy is None for species in species_list):
        logger.warning('Cannot determine if path contains a peak, an E=None')
        return False

    for i, species in enumerate(species_list):

        # Cannot be a peak on the end points
        if i == 0 or i == len(species_list) - 1:
            continue

        # Points either side of this species must be lower in energy
        if all(species_list[k].energy < species.energy
               for k in (i - 1, i + 1)):
            return True

    return False
Exemple #9
0
def get_keywords(calc_input, molecule):
    """Generate a keywords list and adding solvent"""

    new_keywords = []
    scf_block = False

    for keyword in calc_input.keywords:

        if isinstance(keyword, kws.Functional):
            keyword = f'dft\n  maxiter 100\n  xc {keyword.nwchem}\nend'

        elif isinstance(keyword, kws.BasisSet):
            keyword = f'basis\n  *   library {keyword.nwchem}\nend'

        elif isinstance(keyword, kws.Keyword):
            keyword = keyword.nwchem

        if 'opt' in keyword.lower() and molecule.n_atoms == 1:
            logger.warning('Cannot do an optimisation for a single atom')

            # Replace any 'opt' containing word in this keyword with energy
            words = []
            for word in keyword.split():
                if 'opt' in word:
                    words.append('energy')
                else:
                    words.append(word)

            new_keywords.append(' '.join(words))

        elif keyword.lower().startswith('dft'):
            lines = keyword.split('\n')
            lines.insert(1, f'  mult {molecule.mult}')
            new_keyword = '\n'.join(lines)
            new_keywords.append(new_keyword)

        elif keyword.lower().startswith('scf'):
            if calc_input.solvent:
                logger.critical('nwchem only supports solvent for DFT calcs')
                raise UnsuppportedCalculationInput

            scf_block = True
            lines = keyword.split('\n')
            lines.insert(1, f'  nopen {molecule.mult - 1}')
            new_keyword = '\n'.join(lines)
            new_keywords.append(new_keyword)

        elif (any(st in keyword.lower() for st in ['ccsd', 'mp2'])
              and not scf_block):

            if calc_input.solvent.keyword is not None:
                logger.critical('nwchem only supports solvent for DFT calcs')
                raise UnsuppportedCalculationInput

            new_keywords.append(f'scf\n  nopen {molecule.mult - 1}\nend')
            new_keywords.append(keyword)
        else:
            new_keywords.append(keyword)

    return new_keywords
Exemple #10
0
    def get_species_saddle_point(self):
        """Find a TS guess species for this NEB: highest energy saddle point"""
        if self.images.peak_idx is None:
            logger.warning('Found no peaks in the NEB')
            return None

        return self.images[self.images.peak_idx].species
Exemple #11
0
def run_external_monitored(params, output_filename, break_word='MPI_ABORT'):
    """
    Run an external process monitoring the standard output and error for a
    word that will terminate the process

    Arguments:
        params (list(str)):
        output_filename (str):

    Keyword Arguments:
        break_word (str): String that if found will terminate the process
    """
    def output_reader(process, out_file):
        for line in process.stdout:
            if break_word in line.decode('utf-8'):
                raise ChildProcessError

            print(line.decode('utf-8'), end='', file=out_file)

        return None

    with open(output_filename, 'w') as output_file:

        proc = Popen(params, stdout=PIPE, stderr=STDOUT)

        try:
            output_reader(proc, output_file)

        except ChildProcessError:
            logger.warning('External terminated')
            proc.terminate()
            return

    return None
Exemple #12
0
def remove_bonds_invalid_valancies(species):
    """
    Remove invalid valencies for atoms that exceed their maximum valencies e.g.
    H should have no more than 1 'bond'

    Arguments:
        species (autode.species.Species):
    """

    for i in species.graph.nodes:

        max_valance = get_maximal_valance(atom_label=species.atoms[i].label)
        neighbours = list(species.graph.neighbors(i))

        if len(neighbours) <= max_valance:
            # All is well
            continue

        logger.warning(f'Atom {i} exceeds its maximal valence removing edges')

        # Get the atom indexes sorted by the closest to atom i
        closest_atoms = sorted(neighbours,
                               key=lambda j: species.get_distance(i, j))

        # Delete all the bonds to atom(s) j that are above the maximal valance
        for j in closest_atoms[max_valance:]:
            species.graph.remove_edge(i, j)

    return None
Exemple #13
0
    def find_lowest_energy_ts_conformer(self):
        """Find the lowest energy transition state conformer by performing
        constrained optimisations"""
        atoms, energy = deepcopy(self.atoms), deepcopy(self.energy)
        calc = deepcopy(self.optts_calc)

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

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

        self.optimise(name_ext='optts_conf')

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

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

        return None
Exemple #14
0
    def method_string(self):
        """Generate a string with refs (dois) for this method e.g. PBE0-D3BJ"""
        string = ''

        func = self.functional()
        if func is not None:
            string += f'{func.upper()}({func.doi_str()})'

        disp = self.dispersion()
        if disp is not None:
            string += f'-{disp.upper()}({disp.doi_str()})'

        wf = self.wf_method()
        if wf is not None:
            string += f'{str(wf)}({wf.doi_str()})'

        ri = self._get_keyword(keyword_type=RI)
        if ri is not None:
            string += f'({ri.upper()}, {ri.doi_str()})'

        if len(string) == 0:
            logger.warning('Unknown method')
            string = '???'

        return string
Exemple #15
0
def get_distance_constraints(species):
    """Set all the distance constraints required in an optimisation as the
    active bonds

    Arguments:
        species (autode.species.Species):

    Returns:
        (dict): Keyed with atom indexes for the active atoms (tuple) and
                equal to the constrained value
    """
    distance_constraints = {}

    if species.graph is None:
        logger.warning('Molecular graph was not set cannot find any distance '
                       'constraints')
        return None

    # Add the active edges(/bonds) in the molecular graph to the dict, value
    # being the current distance
    for edge in species.graph.edges:

        if species.graph.edges[edge]['active']:
            distance_constraints[edge] = species.distance(*edge)

    return distance_constraints
Exemple #16
0
def get_avg_bond_length(atom_i_label, atom_j_label):
    """Get the average bond length between two atoms with their labels (atomic
    symbols) e.g.

    (atom_i_label='C', atom_j_label ='H') -> 1.1 Å
    (atom_i_label='H', atom_j_label ='C') -> 1.1 Å

    ordering invariant.

    Keyword Arguments:
        atom_i_label (str): atom label e.g 'C'
        atom_j_label (str): atom label e.g 'C'

    Returns:
        (float): Average bond length of the bond (Å)
    """
    key1, key2 = atom_i_label + atom_j_label, atom_j_label + atom_i_label

    if key1 in avg_bond_lengths.keys():
        return avg_bond_lengths[key1]

    elif key2 in avg_bond_lengths.keys():
        return avg_bond_lengths[key2]

    else:
        logger.warning(f'Couldn\'t find a default bond length for '
                       f'({atom_i_label},{atom_j_label}). Using 1.5 Å')
        return 1.5
Exemple #17
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
Exemple #18
0
    def could_have_correct_imag_mode(self, method=None, threshold=-45):
        """
        Determine if a point on the PES could have the correct imaginary mode. This must have

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

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

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

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

        imag_freqs = self.calc.get_imaginary_freqs()

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

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

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

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

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

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

        logger.info('Species could have the correct imaginary mode')
        return True
Exemple #19
0
    def terminated_normally(self):
        """Determine if the calculation terminated without error"""
        logger.info(f'Checking for {self.output.filename} normal termination')

        if not self.output.exists():
            logger.warning('Calculation did not generate any output')
            return False

        return self.method.calculation_terminated_normally(self)
Exemple #20
0
    def get_version(self, calc):
        """Get the version of ORCA used to execute this calculation"""

        for line in calc.output.file_lines:
            if 'Program Version' in line and len(line.split()) >= 3:
                return line.split()[2]

        logger.warning('Could not find the ORCA version number')
        return '???'
Exemple #21
0
    def get_version(self, calc):
        """Get the version of Gaussian used in this calculation"""

        for line in calc.output.file_lines:

            if line.startswith('Gaussian ') and 'Revision' in line:
                return line.lstrip('Gaussian ')

        logger.warning('Could not find the Gaussian version number')
        return '???'
Exemple #22
0
    def get_version(self, calc):
        """Get the XTB version from the output file"""

        for line in calc.output.file_lines:
            if 'xtb version' in line and len(line.split()) >= 4:
                # e.g.   * xtb version 6.2.3 (830e466) compiled by ....
                return line.split()[3]

        logger.warning('Could not find the XTB version in the output file')
        return '???'
Exemple #23
0
    def get_version(self, calc):
        """Get the NWChem version from the output file"""
        for line in calc.output.file_lines:

            if '(NWChem)' in line:
                # e.g. Northwest Computational Chemistry Package (NWChem) 6.6
                return line.split()[-1]

        logger.warning('Could not find the NWChem version')
        return '???'
Exemple #24
0
def save_plot(plot, filename):
    """Save a plot"""

    if os.path.exists(filename):
        logger.warning('Plot already exists. Overriding..')
        os.remove(filename)

    plot.savefig(filename, dpi=400 if Config.high_quality_plots else 100)
    plot.close()

    return None
Exemple #25
0
def species_are_isomorphic(species1, species2):
    """
    Check if two complexes are isomorphic in at least one of their conformers

    Arguments:
        species1 (autode.species.Species):
        species2 (autode.species.Species):

    Returns:
        (bool):
    """
    logger.info(f'Checking if {species1.name} and {species2.name} are '
                f'isomorphic')

    if species1.graph is None or species2.graph is None:
        raise ex.NoMolecularGraph

    if is_isomorphic(species1.graph, species2.graph):
        return True

    if species1.conformers is None and species2.conformers is None:
        logger.warning('Cannot check for isomorphic species conformers')
        return False

    # Conformers don't necessarily have molecular graphs, so make them all
    logger.disabled = True

    for species in (species1, species2):
        if species.conformers is None:
            continue

        for conformer in species.conformers:
            make_graph(conformer)

    logger.disabled = False

    # Check on all the pairwise combinations of species conformers looking for
    #  an isomorphism
    def conformers_or_self(species):
        """If there are no conformers for this species return itself otherwise
        the list of conformers"""
        if species.conformers is None:
            return [species]

        return species.conformers

    # Check on all pairs of conformers between the two species
    for conformer1 in conformers_or_self(species1):
        for conformer2 in conformers_or_self(species2):

            if is_isomorphic(conformer1.graph, conformer2.graph):
                return True

    return False
Exemple #26
0
def calc_delta_with_cont(left, right, cont):
    """Calculate a ∆H or ∆G by adding a contribution to ∆E"""
    de = calc_delta(attr='energy', left=left, right=right)
    d_cont = calc_delta(attr=cont, left=left, right=right)

    if de is None or d_cont is None:
        logger.warning('Could not calculate ∆ either the energy or thermal '
                       'contribution was None')
        return None

    return de + d_cont
Exemple #27
0
    def find_lowest_energy_conformer(self, lmethod=None, hmethod=None):
        """
        For a molecule object find the lowest conformer in energy and set the
        molecule.atoms and molecule.energy

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

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

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

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

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

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

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

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

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

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

        self._set_lowest_energy_conformer()

        logger.info(f'Lowest energy conformer found. E = {self.energy}')
        return None
Exemple #28
0
    def rel_energies(self):
        """
        "Relative energies in a particular unit

        Returns:
            (np.ndarray):
        """
        if len(self) == 0:
            logger.warning('Cannot determine relative energies with no points')
            return np.array([])

        return self.units.conversion * (self.energies - np.min(self.energies))
Exemple #29
0
    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
Exemple #30
0
def init_organic_smiles(molecule, smiles):
    """
    Initialise a molecule from a SMILES string, set the charge, multiplicity (
    if it's not already specified) and the 3D geometry using RDKit

    Arguments:
        molecule (autode.molecule.Molecule):
        smiles (str): SMILES string
    """

    try:
        molecule.rdkit_mol_obj = Chem.MolFromSmiles(smiles)

        if molecule.rdkit_mol_obj is None:
            logger.warning('RDKit failed to initialise a molecule')
            return init_smiles(molecule, smiles)

        molecule.rdkit_mol_obj = Chem.AddHs(molecule.rdkit_mol_obj)
    except RuntimeError:
        raise RDKitFailed

    molecule.charge = Chem.GetFormalCharge(molecule.rdkit_mol_obj)
    molecule.mult = calc_multiplicity(molecule,
                                      NumRadicalElectrons(molecule.rdkit_mol_obj))
    bonds = [(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx())
             for bond in molecule.rdkit_mol_obj.GetBonds()]

    # Generate a single 3D structure using RDKit's ETKDG conformer generation
    # algorithm
    method = AllChem.ETKDGv2()
    method.randomSeed = 0xf00d
    AllChem.EmbedMultipleConfs(molecule.rdkit_mol_obj, numConfs=1, params=method)
    molecule.atoms = atoms_from_rdkit_mol(molecule.rdkit_mol_obj, conf_id=0)
    make_graph(molecule, bond_list=bonds)

    # Revert back to RR if RDKit fails to return a sensible geometry
    if not are_coords_reasonable(coords=molecule.coordinates):
        molecule.rdkit_conf_gen_is_fine = False
        molecule.atoms = get_simanl_atoms(molecule, save_xyz=False)

    for atom, _ in Chem.FindMolChiralCenters(molecule.rdkit_mol_obj):
        molecule.graph.nodes[atom]['stereo'] = True

    for bond in molecule.rdkit_mol_obj.GetBonds():
        if bond.GetBondType() != Chem.rdchem.BondType.SINGLE:
            molecule.graph.edges[bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()]['pi'] = True
        if bond.GetStereo() != Chem.rdchem.BondStereo.STEREONONE:
            molecule.graph.nodes[bond.GetBeginAtomIdx()]['stereo'] = True
            molecule.graph.nodes[bond.GetEndAtomIdx()]['stereo'] = True

    check_bonds(molecule, bonds=molecule.rdkit_mol_obj.GetBonds())

    return None