Exemplo n.º 1
0
def test_are_coords_reasonable():

    good_coords = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
    assert geom.are_coords_reasonable(coords=good_coords) is True

    bad_coords1 = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.5]])
    assert geom.are_coords_reasonable(coords=bad_coords1) is False

    bad_coords2 = np.array([[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0],
                            [0.0, 2.0, 0.0], [2.0, 0.0, 0.0]])
    assert geom.are_coords_reasonable(coords=bad_coords2) is False
Exemplo n.º 2
0
def test_ts_conformer(tmpdir):
    os.chdir(tmpdir)

    ch3cl = Reactant(charge=0,
                     mult=1,
                     atoms=[
                         Atom('Cl', 1.63664, 0.02010, -0.05829),
                         Atom('C', -0.14524, -0.00136, 0.00498),
                         Atom('H', -0.52169, -0.54637, -0.86809),
                         Atom('H', -0.45804, -0.50420, 0.92747),
                         Atom('H', -0.51166, 1.03181, -0.00597)
                     ])
    f = Reactant(charge=-1, mult=1, atoms=[Atom('F', 4.0, 0.0, 0.0)])

    ch3f = Product(charge=0,
                   mult=1,
                   atoms=[
                       Atom('C', -0.05250, 0.00047, -0.00636),
                       Atom('F', 1.31229, -0.01702, 0.16350),
                       Atom('H', -0.54993, -0.04452, 0.97526),
                       Atom('H', -0.34815, 0.92748, -0.52199),
                       Atom('H', -0.36172, -0.86651, -0.61030)
                   ])
    cl = Reactant(charge=-1, mult=1, atoms=[Atom('Cl', 4.0, 0.0, 0.0)])

    f_ch3cl_tsguess = TSguess(reactant=ReactantComplex(f, ch3cl),
                              product=ProductComplex(ch3f, cl),
                              atoms=[
                                  Atom('F', -2.66092, -0.01426, 0.09700),
                                  Atom('Cl', 1.46795, 0.05788, -0.06166),
                                  Atom('C', -0.66317, -0.01826, 0.02488),
                                  Atom('H', -0.78315, -0.58679, -0.88975),
                                  Atom('H', -0.70611, -0.54149, 0.97313),
                                  Atom('H', -0.80305, 1.05409, 0.00503)
                              ])

    f_ch3cl_tsguess.bond_rearrangement = BondRearrangement(breaking_bonds=[
        (2, 1)
    ],
                                                           forming_bonds=[(0,
                                                                           2)])

    f_ch3cl_ts = TransitionState(ts_guess=f_ch3cl_tsguess)

    atoms = conf_gen.get_simanl_atoms(
        species=f_ch3cl_ts, dist_consts=get_distance_constraints(f_ch3cl_ts))

    regen = Molecule(name='regenerated_ts', charge=-1, mult=1, atoms=atoms)
    # regen.print_xyz_file()

    # Ensure the making/breaking bonds retain their length
    regen_coords = regen.get_coordinates()
    assert are_coords_reasonable(regen_coords) is True

    assert 1.9 < np.linalg.norm(regen_coords[0] - regen_coords[2]) < 2.1
    assert 2.0 < np.linalg.norm(regen_coords[1] - regen_coords[2]) < 2.2

    os.chdir(here)
Exemplo n.º 3
0
def test_metal_eta_complex(tmpdir):
    os.chdir(tmpdir)

    # eta-6 benzene Fe2+ complex used in the molassembler paper
    m = Molecule(smiles='[C@@H]12[C@H]3[C@H]4[C@H]5[C@H]6[C@@H]1[Fe]265437N'
                 '(C8=CC=CC=C8)C=CC=[N+]7C9=CC=CC=C9')
    m.print_xyz_file()
    assert are_coords_reasonable(coords=m.get_coordinates())

    os.chdir(here)
Exemplo n.º 4
0
def test_siman_conf_gen(tmpdir):
    os.chdir(tmpdir)

    rh_complex = Molecule(name='[RhH(CO)3(ethene)]',
                          smiles='O=C=[Rh]1(=C=O)(CC1)([H])=C=O')
    assert are_coords_reasonable(coords=rh_complex.get_coordinates())
    assert rh_complex.n_atoms == 14
    assert 12 < rh_complex.graph.number_of_edges() < 15  # What is a bond even

    os.chdir(here)
Exemplo n.º 5
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
Exemplo n.º 6
0
def test_nci_complex():

    water = Molecule(name='water', smiles='O')
    f = Molecule(name='formaldehyde', smiles='C=O')

    nci_complex = NCIComplex(f, water)
    assert nci_complex.n_atoms == 7

    # Set so the number of conformers doesn't explode
    Config.num_complex_sphere_points = 6
    Config.num_complex_random_rotations = 4

    nci_complex._generate_conformers()
    assert len(nci_complex.conformers) == 24

    for conformer in nci_complex.conformers:
        # conformer.print_xyz_file()
        assert geom.are_coords_reasonable(coords=conformer.get_coordinates())
Exemplo n.º 7
0
    def _reasonable_components_with_energy(self):
        """Generator for components of a reaction that have sensible geometries
        and also energies"""

        reacs_prods = self.reacs + self.prods
        for mol in reacs_prods + [self.ts, self.reactant, self.product]:

            if mol is None:
                logger.warning('mol=None')
                continue

            if mol.energy is None:
                logger.warning(f'{mol.name} current energy was None')
                continue

            if not are_coords_reasonable(mol.coordinates):
                logger.warning(f'{mol.name} coordinates not reasonable')
                continue

            yield mol

        return None
Exemplo n.º 8
0
def test_salt():

    salt = Molecule(name='salt', smiles='[Li][Br]')
    assert salt.n_atoms == 2
    assert are_coords_reasonable(coords=salt.get_coordinates())
    os.remove('salt_conf0_siman.xyz')
Exemplo n.º 9
0
def test_salt():

    salt = Molecule(name='salt', smiles='[Li][Br]')
    assert salt.n_atoms == 2
    assert are_coords_reasonable(coords=salt.coordinates)
Exemplo n.º 10
0
def get_simanl_atoms(species, dist_consts=None, conf_n=0):
    """
    Use a bonded + repulsive force field to generate 3D structure for a
    species. If the initial coordinates are reasonable e.g. from a previously
    generated 3D structure then add random displacement vectors and minimise
    to generate a conformer. Otherwise add atoms to the box sequentially
    until all atoms have been added, which generates a qualitatively reasonable
    3D geometry which should be optimised using a electronic structure method

    V(x) = Σ_bonds k(d - d0)^2 + Σ_ij c/d^n

    Arguments:
        species (autode.species.Species):

        dist_consts (dict): Key = tuple of atom indexes, Value = distance

        conf_n (int): Number of this conformer

    Returns:
        (list(autode.atoms.Atom)): Atoms
    """
    xyz_filename = f'{species.name}_conf{conf_n}_siman.xyz'

    saved_atoms = get_atoms_from_generated_file(species, xyz_filename)
    if saved_atoms is not None:
        return saved_atoms

    # To generate the potential requires bonds between atoms defined in a
    # molecular graph
    if species.graph is None:
        raise NoMolecularGraph

    # Initialise a new random seed and make a copy of the species' atoms.
    # RandomState is thread safe
    rand = np.random.RandomState()
    atoms = get_atoms_rotated_stereocentres(species=species,
                                            atoms=deepcopy(species.atoms),
                                            rand=rand)

    # Add the distance constraints as fixed bonds
    d0 = get_ideal_bond_length_matrix(atoms=species.atoms,
                                      bonds=species.graph.edges())

    # Add distance constraints across stereocentres e.g. for a Z double bond
    # then modify d0 appropriately
    dist_consts = add_dist_consts_for_stereocentres(
        species=species,
        dist_consts={} if dist_consts is None else dist_consts)

    constrained_bonds = []
    for bond, length in dist_consts.items():
        i, j = bond
        d0[i, j] = length
        d0[j, i] = length
        constrained_bonds.append(bond)

    # Randomise coordinates that aren't fixed by shifting a maximum of
    # autode.Config.max_atom_displacement in x, y, z
    fixed_atom_indexes = get_non_random_atoms(species=species)

    # Shift by a factor defined in the config file if the coordinates are
    # reasonable but otherwise init in a 10 A cube
    initial_coords_are_reasonable = are_coords_reasonable(
        species.get_coordinates())

    if initial_coords_are_reasonable:
        factor = Config.max_atom_displacement / np.sqrt(3)
        [
            atom.translate(vec=factor * rand.uniform(-1, 1, 3))
            for i, atom in enumerate(atoms) if i not in fixed_atom_indexes
        ]
    else:
        # Randomise in a 10 Å cubic box
        [atom.translate(vec=rand.uniform(-5, 5, 3)) for atom in atoms]

    logger.info('Minimising species...')
    st = time()
    if initial_coords_are_reasonable:
        coords = get_coords_minimised_v(coords=np.array(
            [atom.coord for atom in atoms]),
                                        bonds=species.graph.edges,
                                        k=1.0,
                                        c=0.01,
                                        d0=d0,
                                        tol=1E-5,
                                        fixed_bonds=constrained_bonds)

    else:
        coords = get_coords_no_init_strucutre(atoms, species, d0,
                                              constrained_bonds)

    logger.info(f'                 ... ({time()-st:.3f} s)')

    # Set the coordinates of the new atoms
    for i, atom in enumerate(atoms):
        atom.coord = coords[i]

    # Print an xyz file so rerunning will read the file
    atoms_to_xyz_file(atoms=atoms, filename=xyz_filename)

    return atoms
Exemplo n.º 11
0
def add_dist_consts_for_stereocentres(species, dist_consts):
    """
    Add distances constraints across two bonded stereocentres, for example
    for a Z alkene, (hopefully) ensuring that in the conformer generation the
    stereochemistry is retained. Will also add distance constraints from
    one nearest neighbour to the other nearest neighbours for that chiral
    centre

    Arguments:
        species (autode.species.Species):
        dist_consts (dict): keyed with tuple of atom indexes and valued with
                    the distance (Å), or None

    Returns:
        (dict): Distance constraints
    """
    if not are_coords_reasonable(coords=species.get_coordinates()):
        # TODO generate a reasonable initial structure: molassembler?
        logger.error('Cannot constrain stereochemistry if the initial '
                     'structure is not sensible')
        return dist_consts

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

    # Get the stereocentres with 4 bonds as ~ chiral centres
    chiral_centres = [
        centre for centre in stereocentres
        if len(list(species.graph.neighbors(centre))) == 4
    ]

    # Add distance constraints from one atom to the other 3 atoms to fix the
    # configuration
    for chiral_centre in chiral_centres:
        neighbors = list(species.graph.neighbors(chiral_centre))
        atom_i = neighbors[0]

        for atom_j in neighbors[1:]:
            dist_consts[(atom_i,
                         atom_j)] = species.get_distance(atom_i, atom_j)

    # Check on every pair of stereocenters
    for (atom_i, atom_j) in combinations(stereocentres, 2):

        # If they are not bonded don't alter
        if (atom_i, atom_j) not in species.graph.edges:
            continue

        # Add a single distance constraint between the nearest neighbours of
        # each stereocentre
        for atom_i_neighbour in species.graph.neighbors(atom_i):
            for atom_j_neighbour in species.graph.neighbors(atom_j):
                if atom_i_neighbour != atom_j and atom_j_neighbour != atom_i:

                    # Fix the distance to the current value
                    dist_consts[(atom_i_neighbour,
                                 atom_j_neighbour)] = species.get_distance(
                                     atom_i_neighbour, atom_j_neighbour)

    logger.info(f'Have {len(dist_consts)} distance constraint(s)')
    return dist_consts