Пример #1
0
def reactants_graph(tsg):
    """ get a graph of the reactants from a transition state graph
    """
    frm_bnd_keys = forming_bond_keys(tsg)
    ord_dct = dict_.transform_values(bond_orders(tsg), func=round)
    gra = set_bond_orders(tsg, ord_dct)
    gra = remove_bonds(gra, frm_bnd_keys)
    return gra
Пример #2
0
def resonance_dominant_bond_orders(rgr):
    """ resonance-dominant bond orders, by bond
    """
    rgr = without_fractional_bonds(rgr)
    bnd_keys = list(bond_keys(rgr))
    bnd_ords_by_res = [
        dict_.values_by_key(bond_orders(dom_rgr), bnd_keys)
        for dom_rgr in dominant_resonances(rgr)
    ]
    bnd_ords_lst = list(map(frozenset, zip(*bnd_ords_by_res)))
    bnd_dom_res_ords_dct = dict(zip(bnd_keys, bnd_ords_lst))
    return bnd_dom_res_ords_dct
Пример #3
0
def _add_pi_bonds(rgr, bnd_ord_inc_dct):
    """ add pi bonds to this graph
    """
    bnd_keys = bond_keys(rgr)
    assert set(bnd_ord_inc_dct.keys()) <= bnd_keys

    bnd_keys = list(bnd_keys)
    bnd_ords = dict_.values_by_key(bond_orders(rgr), bnd_keys)
    bnd_ord_incs = dict_.values_by_key(bnd_ord_inc_dct, bnd_keys, fill_val=0)
    new_bnd_ords = numpy.add(bnd_ords, bnd_ord_incs)
    bnd_ord_dct = dict(zip(bnd_keys, new_bnd_ords))
    rgr = set_bond_orders(rgr, bnd_ord_dct)
    return rgr
Пример #4
0
def resonance_avg_bond_orders(rgr):
    """ resonance-dominant bond orders, by bond
    """
    rgr = without_fractional_bonds(rgr)
    bnd_keys = list(bond_keys(rgr))
    bnd_ords_by_res = [
        dict_.values_by_key(bond_orders(dom_rgr), bnd_keys)
        for dom_rgr in dominant_resonances(rgr)
    ]
    nres = len(bnd_ords_by_res)
    bnd_ords_lst = zip(*bnd_ords_by_res)
    avg_bnd_ord_lst = [sum(bnd_ords) / nres for bnd_ords in bnd_ords_lst]
    avg_bnd_ord_dct = dict(zip(bnd_keys, avg_bnd_ord_lst))
    return avg_bnd_ord_dct
Пример #5
0
def from_graph(gra):
    """ networkx graph object from a molecular graph
    """
    nxg = networkx.Graph()
    nxg.add_nodes_from(atom_keys(gra))
    nxg.add_edges_from(bond_keys(gra))
    networkx.set_node_attributes(nxg, atom_symbols(gra), 'symbol')
    networkx.set_node_attributes(nxg, atom_implicit_hydrogen_valences(gra),
                                 'implicit_hydrogen_valence')
    networkx.set_node_attributes(nxg, atom_stereo_parities(gra),
                                 'stereo_parity')
    networkx.set_edge_attributes(nxg, bond_orders(gra), 'order')
    networkx.set_edge_attributes(nxg, bond_stereo_parities(gra),
                                 'stereo_parity')

    return nxg
Пример #6
0
def bonds_of_order(gra, mbond=1):
    """ Determine the indices of all bonds in a molecule with
        the specified bond order.

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param mbond: bond order of desired bond type
        :type mbond: int
    """

    # Build resonance graph to get the bond orders
    gra = dominant_resonance(gra)
    bond_order_dct = bond_orders(gra)

    mbond_idxs = tuple()
    for bond, order in bond_order_dct.items():
        if order == mbond:
            bnd1, bnd2 = bond
            mbond_idxs += ((bnd1, bnd2), )

    return mbond_idxs
Пример #7
0
def subresonances(rgr):
    """ this connected graph and its lower-spin (more pi-bonded) resonances
    """
    rgr = without_fractional_bonds(rgr)
    # get the bond capacities (room for increasing bond order), filtering out
    # the negative ones to avoid complications with hypervalent atoms in TSs
    bnd_cap_dct = dict_.by_value(_bond_capacities(rgr), lambda x: x > 0)

    ret_rgrs = []
    if bnd_cap_dct:
        bnd_keys, bnd_caps = zip(*bnd_cap_dct.items())
        atm_keys = list(functools.reduce(frozenset.union, bnd_keys))

        # Loop over all possible combinations of bond order increments (amounts
        # by which to increase the bond order), filtering out combinations that
        # exceed the valences of the atoms involved.
        # (Note that we are only testing the bonds with available pi electrons,
        # so this is compatible with having hypervalent atoms elsewhere in the
        # molecule)
        bnd_ord_inc_ranges = [range(bnd_cap + 1) for bnd_cap in bnd_caps]
        for bnd_ord_incs in itertools.product(*bnd_ord_inc_ranges):
            bnd_ord_inc_dct = dict(zip(bnd_keys, bnd_ord_incs))
            ret_rgr = _add_pi_bonds(rgr, bnd_ord_inc_dct)

            max_bnd_ord = max(bond_orders(ret_rgr).values())

            atm_unsat_vlcs = dict_.values_by_key(
                atom_unsaturated_valences(ret_rgr), atm_keys)

            if not any(atm_unsat_vlc < 0 for atm_unsat_vlc in atm_unsat_vlcs):
                if max_bnd_ord < 4:
                    ret_rgrs.append(ret_rgr)

    if not ret_rgrs:
        ret_rgrs = (rgr, )
    else:
        ret_rgrs = tuple(ret_rgrs)

    return ret_rgrs
Пример #8
0
def breaking_bond_keys(tsg):
    """ get the forming bonds from a transition state graph
    """
    ord_dct = bond_orders(tsg)
    brk_bnd_keys = [k for k, o in ord_dct.items() if round(o, 1) == 0.9]
    return frozenset(map(frozenset, brk_bnd_keys))
Пример #9
0
def smiles(gra, stereo=True, local_stereo=False, res_stereo=False):
    """ SMILES string from graph

        :param gra: molecular graph
        :type gra: automol graph data structure
        :param stereo: Include stereo?
        :type stereo: bool
        :param local_stereo: Is the graph using local stereo assignments? That
            is, are they based on atom keys rather than canonical keys?
        :type local_stereo: bool
        :param res_stereo: allow resonant double-bond stereo?
        :type res_stereo: bool
        :returns: the SMILES string
        :rtype: str
    """
    assert is_connected(gra), (
        "Cannot form connection layer for disconnected graph.")

    if not stereo:
        gra = without_stereo_parities(gra)

    # If not using local stereo assignments, canonicalize the graph first.
    # From this point on, the stereo parities can be assumed to correspond to
    # the neighboring atom keys.
    if not local_stereo:
        gra = canonical(gra)

    # Convert to implicit graph
    gra = implicit(gra)

    # Insert hydrogens necessary for bond stereo
    gra = _insert_stereo_hydrogens(gra)

    # Find a dominant resonance
    rgr = dominant_resonance(gra)

    # Determine atom symbols
    symb_dct = atom_symbols(rgr)

    # Determine atom implicit hydrogens
    nhyd_dct = atom_implicit_hydrogen_valences(rgr)

    # Determine bond orders for this resonance
    bnd_ord_dct = bond_orders(rgr)

    # Find radical sites for this resonance
    rad_atm_keys = radical_atom_keys_from_resonance(rgr)

    # Determine neighbors
    nkeys_dct = atoms_neighbor_atom_keys(rgr)

    # Find stereo parities
    atm_par_dct = dict_.filter_by_value(atom_stereo_parities(rgr),
                                        lambda x: x is not None)
    bnd_par_dct = dict_.filter_by_value(bond_stereo_parities(rgr),
                                        lambda x: x is not None)

    # Remove stereo parities if requested
    if not res_stereo:
        print('before')
        print(bnd_par_dct)
        bnd_par_dct = dict_.filter_by_key(bnd_par_dct,
                                          lambda x: bnd_ord_dct[x] == 2)
        print('after')
        print(bnd_par_dct)
    else:
        raise NotImplementedError("Not yet implemented!")

    def _atom_representation(key, just_seen=None, nkeys=(), closures=()):
        symb = ptab.to_symbol(symb_dct[key])
        nhyd = nhyd_dct[key]

        needs_brackets = key in rad_atm_keys or symb not in ORGANIC_SUBSET

        hyd_rep = f'H{nhyd}' if nhyd > 1 else ('H' if nhyd == 1 else '')
        par_rep = ''

        if key in atm_par_dct:
            needs_brackets = True

            skeys = [just_seen]
            if nhyd:
                assert nhyd == 1
                skeys.append(-numpy.inf)
            if closures:
                skeys.extend(closures)
            skeys.extend(nkeys)

            can_par = atm_par_dct[key]
            smi_par = can_par ^ util.is_odd_permutation(skeys, sorted(skeys))
            par_rep = '@@' if smi_par else '@'

        if needs_brackets:
            rep = f'[{symb}{par_rep}{hyd_rep}]'
        else:
            rep = f'{symb}'

        return rep

    # Get the pool of stereo bonds for the graph and set up a dictionary for
    # storing the ending representation.
    ste_bnd_key_pool = list(bnd_par_dct.keys())
    drep_dct = {}

    def _bond_representation(key, just_seen=None):
        key0 = just_seen
        key1 = key

        # First, handle the bond order
        if key0 is None or key1 is None:
            rep = ''
        else:
            bnd_ord = bnd_ord_dct[frozenset({key0, key1})]
            if bnd_ord == 1:
                rep = ''
            elif bnd_ord == 2:
                rep = '='
            elif bnd_ord == 3:
                rep = '#'
            else:
                raise ValueError("Bond orders greater than 3 not permitted.")

        drep = drep_dct[(key0, key1)] if (key0, key1) in drep_dct else ''

        bnd_key = next((b for b in ste_bnd_key_pool if key1 in b), None)
        if bnd_key is not None:
            # We've encountered a new stereo bond, so remove it from the pool
            ste_bnd_key_pool.remove(bnd_key)

            # Determine the atoms involved
            key2, = bnd_key - {key1}
            nkey1s = set(nkeys_dct[key1]) - {key2}
            nkey2s = set(nkeys_dct[key2]) - {key1}

            nmax1 = max(nkey1s)
            nmax2 = max(nkey2s)

            nkey1 = just_seen if just_seen in nkey1s else nmax1
            nkey2 = nmax2

            # Determine parity
            can_par = bnd_par_dct[bnd_key]
            smi_par = can_par if nkey1 == nmax1 else not can_par

            # Determine bond directions
            drep1 = drep if drep else '/'
            if just_seen in nkey1s:
                drep = drep1
                flip = not smi_par
            else:
                drep_dct[(key1, nkey1)] = drep1
                flip = smi_par

            drep2 = _flip_direction(drep1, flip=flip)

            drep_dct[(key2, nkey2)] = drep2

        rep += drep

        # Second, handle directionality (bond stereo)
        return rep

    # Get the pool of rings for the graph and set up a dictionary for storing
    # their tags. As the SMILES is built, each next ring that is encountered
    # will be given a tag, removed from the pool, and transferred to the tag
    # dictionary.
    rng_pool = list(rings_atom_keys(rgr))
    rng_tag_dct = {}

    def _ring_representation_with_nkeys_and_closures(key, nkeys=()):
        nkeys = nkeys.copy()

        # Check for new rings in the ring pool. If a new ring is found, create
        # a tag, add it to the tags dictionary, and drop it from the rings
        # pool.
        for new_rng in rng_pool:
            if key in new_rng:
                # Choose a neighbor key for SMILES ring closure
                clos_nkey = sorted(set(new_rng) & set(nkeys))[0]

                # Add it to the ring tag dictionary with the current key first
                # and the closure key last
                tag = max(rng_tag_dct.values(), default=0) + 1
                assert tag < 10, (
                    f"Ring tag exceeds 10 for this graph:\n{string(gra)}")
                rng = cycle_ring_atom_key_to_front(new_rng, key, clos_nkey)
                rng_tag_dct[rng] = tag

                # Remove it from the pool of unseen rings
                rng_pool.remove(new_rng)

        tags = []
        closures = []
        for rng, tag in rng_tag_dct.items():
            if key == rng[-1]:
                nkeys.remove(rng[0])
                closures.append(rng[0])
                # Handle the special case where the last ring bond has stereo
                if (rng[-1], rng[0]) in drep_dct:
                    drep = drep_dct[(rng[-1], rng[0])]
                    tags.append(f'{drep}{tag}')
                else:
                    tags.append(f'{tag}')
            if key == rng[0]:
                nkeys.remove(rng[-1])
                closures.append(rng[-1])
                tags.append(f'{tag}')

        rrep = ''.join(map(str, tags))
        return rrep, nkeys, closures

    # Determine neighboring keys
    nkeys_dct_pool = dict_.transform_values(atoms_neighbor_atom_keys(rgr),
                                            sorted)

    def _recurse_smiles(smi, lst, key, just_seen=None):
        nkeys = nkeys_dct_pool.pop(key) if key in nkeys_dct_pool else []

        # Remove keys just seen from the list of neighbors, to avoid doubling
        # back.
        if just_seen in nkeys:
            nkeys.remove(just_seen)

        # Start the SMILES string and connection list. The connection list is
        # used for sorting.
        rrep, nkeys, closures = _ring_representation_with_nkeys_and_closures(
            key, nkeys)
        arep = _atom_representation(key, just_seen, nkeys, closures=closures)
        brep = _bond_representation(key, just_seen)
        smi = f'{brep}{arep}{rrep}'
        lst = [key]

        # Now, extend the layer/list along the neighboring atoms.
        if nkeys:
            # Build sub-strings/lists by recursively calling this function.
            sub_smis = []
            sub_lsts = []
            while nkeys:
                nkey = nkeys.pop(0)
                sub_smi, sub_lst = _recurse_smiles('', [], nkey, just_seen=key)

                sub_smis.append(sub_smi)
                sub_lsts.append(sub_lst)

                # If this is a ring, remove the neighbor on the other side of
                # `key` to prevent repetition as we go around the ring.
                if sub_lst[-1] == key:
                    nkeys.remove(sub_lst[-2])

            # Now, join the sub-layers and lists together.
            # If there is only one neighbor, we joint it as
            #   {arep1}{brep2}{arep2}...
            if len(sub_lsts) == 1:
                sub_smi = sub_smis[0]
                sub_lst = sub_lsts[0]

                # Extend the SMILES string
                smi += f'{sub_smi}'

                # Extend the list
                lst.extend(sub_lst)
            # If there are multiple neighbors, we joint them as
            #   {arep1}({brep2}{arep2}...)({brep3}{arep3}...){brep4}{arep4}...
            else:
                assert len(sub_lsts) > 1

                # Extend the SMILES string
                smi += (''.join(map("({:s})".format, sub_smis[:-1])) +
                        sub_smis[-1])

                # Append the lists of neighboring branches.
                lst.append(sub_lsts)

        return smi, lst

    # If there are terminal atoms, start from the first one
    atm_keys = atom_keys(rgr)
    term_keys = terminal_atom_keys(gra, heavy=False)
    start_key = min(term_keys) if term_keys else min(atm_keys)

    smi, _ = _recurse_smiles('', [], start_key)

    return smi