Example #1
0
def template_matches(reactant, truncated_graph, ts_template):
    """
    Determine if a transition state template matches a truncated graph. The
    truncated graph includes all the active bonds in the reaction and the
    nearest neighbours to those atoms e.g. for a Diels-Alder reaction
         H        H
          \      /
        H-C----C-H          where the dotted lines represent active bonds
          .    .
      H  .      .   H
       \.        . /
    H - C        C - H
         \      /
          C    C

    Arguments:
        reactant (autode.complex.ReactantComplex):
        truncated_graph (nx.Graph):
        ts_template (autode.transition_states.templates.TStemplate):
    """

    if reactant.charge != ts_template.charge or reactant.mult != ts_template.mult:
        return False

    if reactant.solvent != ts_template.solvent:
        return False

    if is_isomorphic(truncated_graph, ts_template.graph):
        logger.info('Found matching TS template')
        return True

    return False
Example #2
0
def test_not_isomorphic():

    h_c = Atom(atomic_symbol='H', x=0.0, y=0.0, z=1.0)
    h2_b = Species(name='template', charge=0, mult=1, atoms=[h_a, h_c])
    mol_graphs.make_graph(species=h2_b, rel_tolerance=0.3)

    assert mol_graphs.is_isomorphic(h2.graph, h2_b.graph) is False
Example #3
0
def test_not_isomorphic2():

    c = Atom(atomic_symbol='C', x=0.0, y=0.0, z=0.7)
    ch = Species(name='ch', atoms=[h_a, c], charge=0, mult=2)
    mol_graphs.make_graph(ch)

    assert mol_graphs.is_isomorphic(h2.graph, ch.graph) is False
Example #4
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
Example #5
0
def test_reac_to_prods():

    rearrang = BondRearrangement([(0, 4)], [(3, 4)])
    prod_graph = mol_graphs.reac_graph_to_prod_graph(g, rearrang)
    expected_edges = [(0, 1), (1, 2), (2, 0), (0, 3), (0, 4)]
    expected_graph = nx.Graph()
    for edge in expected_edges:
        expected_graph.add_edge(*edge)

    assert mol_graphs.is_isomorphic(expected_graph, prod_graph)
def test_generate_rearranged_graph():
    init_graph = nx.Graph()
    final_graph = nx.Graph()
    init_edges = [(0, 1), (1, 2), (2, 3), (4, 5), (5, 6)]
    final_edges = [(0, 1), (2, 3), (3, 4), (4, 5), (5, 6)]
    for edge in init_edges:
        init_graph.add_edge(*edge)
    for edge in final_edges:
        final_graph.add_edge(*edge)
    assert is_isomorphic(br.generate_rearranged_graph(init_graph, [(3, 4)], [(1, 2)]), final_graph)
Example #7
0
def add_bond_rearrangment(bond_rearrangs, reactant, product, fbonds, bbonds):
    """For a possible bond rearrangement, sees if the products are made, and
    adds it to the bond rearrang list if it does

    Arguments:
        bond_rearrangs (list(autode.bond_rearrangements.BondRearrangement)):
                        list of working bond rearrangments
        reactant (molecule object): reactant complex
        product (molecule object): product complex
        fbonds (list(tuple)): list of bonds to be made
        bbonds (list(tuple)): list of bonds to be broken

    Returns:
        (list(autode.bond_rearrangements.BondRearrangement)):
    """

    # Check that the bond rearrangement doesn't exceed standard atom valances
    bbond_atoms = [atom for bbond in bbonds for atom in bbond]
    for fbond in fbonds:
        for atom in fbond:
            atom_label = reactant.atoms[atom].label

            if (reactant.graph.degree(atom) == get_maximal_valance(atom_label)
                    and atom not in bbond_atoms):
                # If we are here then there is at least one atom that will
                # exceed it's maximal valance, therefore
                # we don't need to run isomorphism
                return bond_rearrangs

    rearranged_graph = generate_rearranged_graph(reactant.graph,
                                                 fbonds=fbonds,
                                                 bbonds=bbonds)

    if is_isomorphic(rearranged_graph, product.graph):
        ordered_fbonds = []
        ordered_bbonds = []
        for fbond in fbonds:
            if fbond[0] < fbond[1]:
                ordered_fbonds.append((fbond[0], fbond[1]))
            else:
                ordered_fbonds.append((fbond[1], fbond[0]))
        for bbond in bbonds:
            if bbond[0] < bbond[1]:
                ordered_bbonds.append((bbond[0], bbond[1]))
            else:
                ordered_bbonds.append((bbond[1], bbond[0]))

        ordered_fbonds.sort()
        ordered_bbonds.sort()
        bond_rearrangs.append(
            BondRearrangement(forming_bonds=ordered_fbonds,
                              breaking_bonds=ordered_bbonds))

    return bond_rearrangs
Example #8
0
    def products_made(self):
        logger.info('Checking that somewhere on the surface product(s) are made')

        for i in range(self.n_points):
            make_graph(self.species[i])

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

        return False
Example #9
0
def test_isomorphic_no_active():
    os.chdir(os.path.join(here, 'data'))

    ts_syn = Conformer(name='syn_ts', charge=-1, mult=0, atoms=xyz_file_to_atoms('E2_ts_syn.xyz'))
    mol_graphs.make_graph(ts_syn)
    mol_graphs.set_active_mol_graph(ts_syn, active_bonds=[(8, 5), (0, 5), (1, 2)])

    ts_anti = Conformer(name='anti_ts', charge=-1, mult=0, atoms=xyz_file_to_atoms('E2_ts.xyz'))
    mol_graphs.make_graph(ts_anti)

    assert mol_graphs.is_isomorphic(ts_syn.graph, ts_anti.graph, ignore_active_bonds=True)

    os.chdir(here)
Example #10
0
def test_core_strip():
    bond_rearr = BondRearrangement()
    bond_rearr.active_atoms = [0]

    stripped = get_truncated_complex(methane, bond_rearr)
    # Should not strip any atoms if the carbon is designated as active
    assert stripped.n_atoms == 5

    stripped = get_truncated_complex(ethene, bond_rearr)
    assert stripped.n_atoms == 6

    bond_rearr.active_atoms = [1]
    # Propene should strip to ethene if the terminal C=C is the active atom
    stripped = get_truncated_complex(propene, bond_rearr)
    assert stripped.n_atoms == 6
    assert is_isomorphic(stripped.graph, ethene.graph)

    # But-1-ene should strip to ethene if the terminal C=C is the active atom
    stripped = get_truncated_complex(but1ene, bond_rearr)
    assert stripped.n_atoms == 6
    assert is_isomorphic(stripped.graph, ethene.graph)

    # Benzene shouldn't be truncated at all
    stripped = get_truncated_complex(benzene, bond_rearr)
    assert stripped.n_atoms == 12

    bond_rearr.active_atoms = [0]
    # Ethanol with the terminal C as the active atom should not replace the OH
    # with a H
    stripped = get_truncated_complex(ethanol, bond_rearr)
    assert stripped.n_atoms == 9

    # Ether with the terminal C as the active atom should replace the OMe with
    # OH
    stripped = get_truncated_complex(methlyethylether, bond_rearr)
    assert stripped.n_atoms == 9
    assert is_isomorphic(stripped.graph, ethanol.graph)
Example #11
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
Example #12
0
def test_ts_template():

    h_c = Atom(atomic_symbol='H', x=0.0, y=0.0, z=1.4)

    ts_template = Species(name='template', charge=0, mult=1,
                          atoms=[h_a, h_b, h_c])
    mol_graphs.make_graph(species=ts_template, allow_invalid_valancies=True)
    ts_template.graph.edges[0, 1]['active'] = True

    ts = Species(name='template', charge=0, mult=1, atoms=[h_a, h_b, h_c])
    mol_graphs.make_graph(species=ts, allow_invalid_valancies=True)
    ts.graph.edges[1, 2]['active'] = True

    mapping = mol_graphs.get_mapping_ts_template(ts.graph, ts_template.graph)
    assert mapping is not None
    assert type(mapping) == dict

    assert mol_graphs.is_isomorphic(ts.graph, ts_template.graph, ignore_active_bonds=True)
Example #13
0
    def products_made(self, product):
        """Check whether the products are made on the surface

        Arguments:
            product (autode.species.Species):

        Returns:
            (bool):
        """
        if product is None or product.graph is None:
            logger.warning('Cannot check if products are made')
            return False

        for i, point in enumerate(self):
            if mol_graphs.is_isomorphic(graph1=point.species.graph,
                                        graph2=product.graph):
                logger.info(f'Products made at point {i}')
                return True

        return False
Example #14
0
def test_timeout():

    # Generate a large-ish graph
    graph = nx.Graph()
    for i in range(10000):
        graph.add_node(i)

    for _ in range(5000):
        (i, j) = np.random.randint(0, 1000, size=2)

        if (i, j) not in graph.edges:
            graph.add_edge(i, j)

    node_perm = np.random.permutation(list(graph.nodes))
    mapping = {u: v for (u, v) in zip(graph.nodes, node_perm)}

    isomorphic_graph = nx.relabel_nodes(graph, mapping=mapping, copy=True)

    # With a short timeout this should return False - not sure this is the
    # optimal behavior
    assert not mol_graphs.is_isomorphic(graph, isomorphic_graph, timeout=1)
Example #15
0
def test_isomorphic_graphs():
    h2_alt = Species(name='H2', atoms=[h_b, h_a], charge=0, mult=1)
    mol_graphs.make_graph(h2_alt)

    assert mol_graphs.is_isomorphic(h2.graph, h2_alt.graph) is True
Example #16
0
def get_sum_energy_mep(saddle_point_r1r2, pes_2d):
    """
    Calculate the sum of the minimum energy path that traverses reactants (r)
    to products (p) via the saddle point (s)::

                /          p
               /     s
          r2  /
             /r
             ------------
                  r1

    Arguments:
        saddle_point_r1r2 (tuple(float)):

        pes_2d (autode.pes_2d.PES2d):

    Returns:
        (float): Path energy (Ha)
    """
    logger.info('Finding the total energy along the minimum energy pathway')

    reactant_point = (0, 0)
    product_point, product_energy = None, 9999

    # The saddle point indexes are those that are closest tp the saddle points
    # r1 and r2 distances
    saddle_point = (np.argmin(np.abs(pes_2d.r1s - saddle_point_r1r2[0])),
                    np.argmin(np.abs(pes_2d.r2s - saddle_point_r1r2[1])))

    # Generate a grid graph (i.e. all nodes are corrected
    energy_graph = nx.grid_2d_graph(pes_2d.n_points_r1, pes_2d.n_points_r2)

    min_energy = min([species.energy for species in pes_2d.species.flatten()])

    # For energy point on the 2D surface
    for i in range(pes_2d.n_points_r1):
        for j in range(pes_2d.n_points_r2):
            point_rel_energy = pes_2d.species[i, j].energy - min_energy

            # Populate the relative energy of each node in the graph
            energy_graph.nodes[i, j]['energy'] = point_rel_energy

            # Find the point where products are made
            if is_isomorphic(graph1=pes_2d.species[i, j].graph,
                             graph2=pes_2d.product_graph):

                # If products have not yet found, or they have and the energy
                # are lower but are still isomorphic
                if product_point is None or point_rel_energy < product_energy:
                    product_point = (i, j)
                    product_energy = point_rel_energy

    logger.info(f'Reactants at r1={pes_2d.r1s[0]:.4f} , '
                f'r2={pes_2d.r2s[0]:.4f} Å and '
                f'products r1={pes_2d.rs[product_point][0]:.4f}, '
                f'r2={pes_2d.rs[product_point][1]:.4f} Å')

    def energy_diff(curr_node, final_node, d):
        """Energy difference between the twp points on the graph. d is required
         to satisfy nx. Must only increase in energy to a saddle point so take
          the magnitude to prevent traversing s mistakenly"""
        return (np.abs(energy_graph.nodes[final_node]['energy'] -
                       energy_graph.nodes[curr_node]['energy']))

    # Calculate the energy along the MEP up to the saddle point from reactants
    # and products
    path_energy = 0.0

    for point in (reactant_point, product_point):
        path_energy += nx.dijkstra_path_length(energy_graph,
                                               source=point,
                                               target=saddle_point,
                                               weight=energy_diff)

    logger.info(f'Path energy to {saddle_point} is {path_energy:.4f} Hd')
    return path_energy
Example #17
0
def get_bond_rearrangs(reactant, product, name, save=True):
    """For a reactant and product (complex) find the set of breaking and
    forming bonds that will turn reactants into products. This works by
    determining the types of bonds that have been made/broken (i.e CH) and
    then only considering rearrangements involving those bonds.

    Arguments:
        reactant (autode.complex.ReactantComplex):
        product (autode.complex.ProductComplex):
        name (str):

    Keyword Arguments:
        save (bool): Save bond rearrangements to a file for fast reloading

    Returns:
        (list(autode.bond_rearrangements.BondRearrangement)):
    """
    logger.info(f'Finding the possible forming and breaking bonds for {name}')

    if os.path.exists(f'{name}_bond_rearrangs.txt'):
        return get_bond_rearrangs_from_file(f'{name}_bond_rearrangs.txt')

    if is_isomorphic(reactant.graph, product.graph) and product.n_atoms > 3:
        logger.error('Reactant (complex) is isomorphic to product (complex). '
                     'Bond rearrangement cannot be determined unless the '
                     'substrates are limited in size')
        return None

    possible_brs = []

    reac_bond_dict = get_bond_type_list(reactant.graph)
    prod_bond_dict = get_bond_type_list(product.graph)

    # list of bonds where this type of bond (e.g C-H) has less bonds in
    # products than reactants
    all_possible_bbonds = []

    # list of bonds that can be formed of this bond type. This is only used
    # if there is only one type of bbond, so can be overwritten for each new
    # type of bbond
    bbond_atom_type_fbonds = None

    # list of bonds where this type of bond (e.g C-H) has more bonds in
    #  products than reactants
    all_possible_fbonds = []

    # list of bonds that can be broken of this bond type. This is only used
    # if there is only one type of fbond, so can be overwritten for each new
    # type of fbond
    fbond_atom_type_bbonds = None

    # list of bonds where this type of bond (e.g C-H) has the same number of
    # bonds in products and reactants
    possible_bbond_and_fbonds = []

    for reac_key, reac_bonds in reac_bond_dict.items():
        prod_bonds = prod_bond_dict[reac_key]
        possible_fbonds = get_fbonds(reactant.graph, reac_key)
        if len(prod_bonds) < len(reac_bonds):
            all_possible_bbonds.append(reac_bonds)
            bbond_atom_type_fbonds = possible_fbonds
        elif len(prod_bonds) > len(reac_bonds):
            all_possible_fbonds.append(possible_fbonds)
            fbond_atom_type_bbonds = reac_bonds
        else:
            if len(reac_bonds) != 0:
                possible_bbond_and_fbonds.append([reac_bonds, possible_fbonds])

    # The change in the number of bonds is > 0 as in the reaction
    # initialisation reacs/prods are swapped if this is < 0
    delta_n_bonds = (reactant.graph.number_of_edges() -
                     product.graph.number_of_edges())

    if delta_n_bonds == 0:
        funcs = [get_fbonds_bbonds_1b1f, get_fbonds_bbonds_2b2f]
    elif delta_n_bonds == 1:
        funcs = [get_fbonds_bbonds_1b, get_fbonds_bbonds_2b1f]
    elif delta_n_bonds == 2:
        funcs = [get_fbonds_bbonds_2b]
    else:
        logger.error(f'Cannot treat a change in bonds '
                     f'reactant <- product of {delta_n_bonds}')
        return None

    for func in funcs:
        possible_brs = func(reactant, product, possible_brs,
                            all_possible_bbonds, all_possible_fbonds,
                            possible_bbond_and_fbonds, bbond_atom_type_fbonds,
                            fbond_atom_type_bbonds)

        if len(possible_brs) > 0:
            logger.info(f'Found a molecular graph rearrangement to products '
                        f'with {func.__name__}')
            # This function will return with the first bond rearrangement
            # that leads to products

            n_bond_rearrangs = len(possible_brs)
            if n_bond_rearrangs > 1:
                logger.info(f'Multiple *{n_bond_rearrangs}* possible bond '
                            f'breaking/makings are possible')
                possible_brs = strip_equiv_bond_rearrs(possible_brs, reactant)
                prune_small_ring_rearrs(possible_brs, reactant)

            if save:
                save_bond_rearrangs_to_file(possible_brs,
                                            filename=f'{name}_BRs.txt')

            logger.info(f'Found *{len(possible_brs)}* bond '
                        f'rearrangement(s) that lead to products')
            return possible_brs

    return None