Ejemplo n.º 1
0
def bonds_of_type(gra, symb1, symb2, mbond=1):
    """ Determine the indices of all a specific bond
        specified by atom type and bond order.

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param symb1: symbol of atom 1 in the bond
        :type symb1: str
        :param symb2: symbol of atom 2 in the bond
        :type symb2: str
        :param mbond: bond order of desired bond type
        :type mbond: int
        :rtype: tuple(int)
    """

    # Get the dict that relates atom indices to symbols
    idx_symb_dct = atom_symbols(gra)

    # Loop over all the bonds and build a list of ones that match
    _bonds_of_type = tuple()

    _bonds = bonds_of_order(gra, mbond=mbond)
    for bond in _bonds:
        idx1, idx2 = bond
        symb1, symb2 = idx_symb_dct[idx1], idx_symb_dct[idx2]
        if (symb1, symb2) in ((symb1, symb2), (symb2, symb1)):
            _bonds_of_type += ((idx1, idx2), )

    return _bonds_of_type
Ejemplo n.º 2
0
def _radical_graph_isomorphisms(gra):
    """ Generate a set of graphs where the radical has been migrated to a
        new atom and then calculate the isomorphism between the input
        graph and all of the new graphs generated by moving the radical
    """

    # Determine useful keys
    symbols = atom_symbols(gra)
    unsat_keys = unsaturated_atom_keys(gra)
    unsat_key = next(iter(unsat_keys))
    h_atm_key = max(symbols.keys()) + 1

    new_gras, isomorphisms = [], []
    for aidx, symbol in enumerate(symbols.values()):

        # Loop over saturated (non-radical) heavy atoms
        if symbol != 'H' and aidx != unsat_key:

            # Add hydrogen atom to radical atom
            new_graph = add_atom_explicit_hydrogen_keys(
                gra, {unsat_key: [h_atm_key]})

            # Remove hydrogen from saturated atom
            neighbors = atoms_neighbor_atom_keys(new_graph)
            for neigh in neighbors[aidx]:
                if symbols[neigh] == 'H':
                    aneighbor = neigh
                    break
            new_graph = remove_atoms(new_graph, [aneighbor])

            # Build lists to return
            new_gras.append(new_graph)
            isomorphisms.append(full_isomorphism(gra, new_graph))

    return tuple(zip(new_gras, isomorphisms))
Ejemplo n.º 3
0
def _extend_chain_to_include_anchoring_atoms(gra, keys, zma_keys):
    """ extend chain to include three atoms already specified in v-matrix

    :param gra: the graph
    :param keys: keys in the chain; the first atom should already be specified
    :param zma_keys: keys currently in the v-matrix
    """
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ))

    symb_dct = atom_symbols(gra)

    key3 = keys[0]
    assert key3 in zma_keys
    key2 = next(k for k in ngb_keys_dct[key3] if k in zma_keys)

    if symb_dct[key2] == 'X':
        key1 = next(k for k in ngb_keys_dct[key3][1:] if k in zma_keys)
    else:
        key1 = next(k for k in ngb_keys_dct[key2]
                    if k in zma_keys and k != key3)

    keys = (
        key1,
        key2,
    ) + tuple(keys)

    return keys
Ejemplo n.º 4
0
def heavy_atom_count(gra, dummy=False):
    """ the number of heavy atoms
    """
    if not dummy:
        gra = without_dummy_atoms(gra)
    atm_symb_dct = atom_symbols(gra)
    nhvy_atms = sum(ptab.to_number(sym) != 1 for sym in atm_symb_dct.values())
    return nhvy_atms
Ejemplo n.º 5
0
def van_der_waals_radius(gra, key):
    """ van der waals radius for an atom in the graph (in angstroms)
    """

    symb_dct = atom_symbols(gra)
    symb = symb_dct[key]
    rad = ptab.van_der_waals_radius(symb) * phycon.BOHR2ANG

    return rad
Ejemplo n.º 6
0
def rotational_bond_keys(gra, lin_keys=None, with_h_rotors=True):
    """ get all rotational bonds for a graph

    :param gra: the graph
    :param lin_keys: keys to linear atoms in the graph
    """
    gra = explicit(gra)
    sym_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    bnd_ord_dct = resonance_dominant_bond_orders(gra)
    rng_bnd_keys = list(itertools.chain(*rings_bond_keys(gra)))

    def _is_rotational_bond(bnd_key):
        ngb_keys_lst = [ngb_keys_dct[k] - bnd_key for k in bnd_key]

        is_single = max(bnd_ord_dct[bnd_key]) <= 1
        has_neighbors = all(ngb_keys_lst)
        not_in_ring = bnd_key not in rng_bnd_keys

        is_h_rotor = any(
            set(map(sym_dct.__getitem__, ks)) == {'H'} for ks in ngb_keys_lst)

        return is_single and has_neighbors and not_in_ring and (
            not is_h_rotor or with_h_rotors)

    rot_bnd_keys = frozenset(filter(_is_rotational_bond, bond_keys(gra)))

    lin_keys_lst = linear_segments_atom_keys(gra, lin_keys=lin_keys)
    dum_keys = tuple(atom_keys(gra, sym='X'))
    for keys in lin_keys_lst:
        bnd_keys = sorted((k for k in rot_bnd_keys if k & set(keys)),
                          key=sorted)

        # Check whether there are neighboring atoms on either side of the
        # linear segment
        excl_keys = set(keys) | set(dum_keys)

        end_key1 = atom_neighbor_atom_key(gra,
                                          keys[0],
                                          excl_atm_keys=excl_keys)

        excl_keys |= {end_key1}
        end_key2 = atom_neighbor_atom_key(gra,
                                          keys[-1],
                                          excl_atm_keys=excl_keys)

        end_keys = {end_key1, end_key2}
        ngb_keys_lst = [ngb_keys_dct[k] - excl_keys for k in end_keys]
        has_neighbors = all(ngb_keys_lst)

        if not has_neighbors:
            rot_bnd_keys -= set(bnd_keys)
        else:
            rot_bnd_keys -= set(bnd_keys[:-1])

    return rot_bnd_keys
Ejemplo n.º 7
0
def atom_count_by_type(gra, sym, keys=None):
    """ count the number of atoms with a given type (symbol)

    :param gra: the graph
    :param sym: the symbol
    :param keys: optionally, restrict the count to a subset of keys
    """
    keys = atom_keys(gra) if keys is None else keys
    symb_dct = atom_symbols(gra)
    symbs = list(map(symb_dct.__getitem__, keys))
    return symbs.count(sym)
Ejemplo n.º 8
0
def formula(gra):
    """ Generate a stoichiometric formula dictionary from a molecular graph.

        :param gra: molecular graph
        :type gra: automol graph data structure
        :type: dict[str: int]
    """

    gra = explicit(gra)
    syms = atom_symbols(gra).values()
    fml = _util.formula(syms)

    return fml
Ejemplo n.º 9
0
def equivalent_bonds(gra, bnd_key, stereo=True, dummy=True):
    """ Identify sets of isomorphically equivalent bonds

    Two bonds are equivalent if they transform into each other under an
    automorphism

    :param gra: A graph
    :param bnd_key: An bond key for the graph, which may be sorted or unsorted
    :param backbone_only: Compare backbone atoms only?
    :type stereo: bool
    :param dummy: Consider dummy atoms?
    :type dummy: bool
    :returns: Keys to equivalent bonds
    :rtype: frozenset
    """
    bnd_key = tuple(bnd_key)
    bnd_keys = list(map(tuple, map(sorted, bond_keys(gra))))
    bnd_keys += list(map(tuple, map(reversed, bnd_keys)))
    assert bnd_key in bnd_keys, "{} not in {}".format(bnd_key, bnd_keys)

    atm_symb_dct = atom_symbols(gra)
    atm_ngbs_dct = atoms_neighbor_atom_keys(gra)

    def _symbols(bnd_key):
        return list(map(atm_symb_dct.__getitem__, bnd_key))

    def _neighbor_symbols(bnd_key):
        key1, key2 = bnd_key
        nsymbs1 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key1]))
        nsymbs2 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key2]))
        return nsymbs1, nsymbs2

    # 1. Find bonds with the same atom types
    bnd_symbs = _symbols(bnd_key)
    cand_keys = [k for k in bnd_keys if _symbols(k) == bnd_symbs]

    # 2. Of those, find bonds with the same neighboring atom types
    bnd_ngb_symbs = _neighbor_symbols(bnd_key)
    cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == bnd_ngb_symbs]

    # 3. Find the equivalent bonds from the list of candidates.
    # Strategy: Change the atom symbols to 'Lv' and 'Ts' and check for
    # isomorphism.  Assumes none of the compounds have element 116 or 117.
    ref_gra = set_atom_symbols(gra, {bnd_key[0]: 'Lv', bnd_key[1]: 'Ts'})
    bnd_keys = []
    for key in cand_keys:
        comp_gra = set_atom_symbols(gra, {key[0]: 'Lv', key[1]: 'Ts'})
        if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy):
            bnd_keys.append(key)

    return frozenset(bnd_keys)
Ejemplo n.º 10
0
def start_at(gra, key):
    """ start a v-matrix at a specific atom

    Returns the started vmatrix, along with keys to atoms whose neighbors are
    missing from it
    """
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ))

    ngb_keys = ngb_keys_dct[key]
    if not ngb_keys:
        zma_keys = []
    elif len(ngb_keys) == 1:
        # Need special handling for atoms with only one neighbor
        if symb_dct[key] in ('H', 'X'):
            key2 = ngb_keys[0]
            zma_keys = (key2, ) + ngb_keys_dct[key2]
        else:
            key2 = ngb_keys[0]
            ngb_keys = tuple(k for k in ngb_keys_dct[key2] if k != key)
            zma_keys = (key, key2) + ngb_keys
    else:
        zma_keys = (key, ) + ngb_keys_dct[key]

    vma = ()
    for row, key_ in enumerate(zma_keys):
        idx1 = idx2 = idx3 = None
        if row > 0:
            key1 = next(k for k in ngb_keys_dct[key_] if k in zma_keys[:row])
            idx1 = zma_keys.index(key1)
        if row > 1:
            key2 = next(k for k in ngb_keys_dct[key1]
                        if k in zma_keys[:row] and k != key_)
            idx2 = zma_keys.index(key2)
        if row > 2:
            key3 = next(k for k in zma_keys[:row]
                        if k not in (key_, key1, key2))
            idx3 = zma_keys.index(key3)

        sym = symb_dct[key_]
        key_row = [idx1, idx2, idx3]
        vma = automol.vmat.add_atom(vma, sym, key_row)

    return vma, zma_keys
Ejemplo n.º 11
0
def geometry(gra, keys=None, ntries=5, max_dist_err=0.2):
    """ sample a qualitatively-correct stereo geometry

    :param gra: the graph, which may or may not have stereo
    :param keys: graph keys, in the order in which they should appear in the
        geometry
    :param ntries: number of tries for finding a valid geometry
    :param max_dist_err: maximum distance error convergence threshold

    Qualitatively-correct means it has the right connectivity and the right
    stero parities, but its bond lengths and bond angles may not be
    quantitatively realistic
    """
    assert gra == explicit(gra), (
        "Graph => geometry conversion requires explicit hydrogens!\n"
        "Use automol.graph.explicit() to convert to an explicit graph.")

    # 0. Get keys and symbols
    symb_dct = atom_symbols(gra)

    keys = sorted(atom_keys(gra)) if keys is None else keys
    symbs = tuple(map(symb_dct.__getitem__, keys))

    # 1. Generate bounds matrices
    lmat, umat = distance_bounds_matrices(gra, keys)
    chi_dct = chirality_constraint_bounds(gra, keys)
    pla_dct = planarity_constraint_bounds(gra, keys)
    conv1_ = qualitative_convergence_checker_(gra, keys)
    conv2_ = embed.distance_convergence_checker_(lmat, umat, max_dist_err)

    def conv_(xmat, err, grad):
        return conv1_(xmat, err, grad) & conv2_(xmat, err, grad)

    # 2. Generate coordinates with correct stereo, trying a few times
    for _ in range(ntries):
        xmat = embed.sample_raw_distance_coordinates(lmat, umat, dim4=True)
        xmat, conv = embed.cleaned_up_coordinates(
            xmat, lmat, umat, pla_dct=pla_dct, chi_dct=chi_dct, conv_=conv_)
        if conv:
            break

    if not conv:
        raise error.FailedGeometryGenerationError

    # 3. Generate a geometry data structure from the coordinates
    xyzs = xmat[:, :3]
    geo = _create.geom.from_data(symbs, xyzs, angstrom=True)

    return geo
Ejemplo n.º 12
0
def atom_symbol_idxs(gra):
    """ determine the indices for each atom symbol
        :return: dict[symb] = [idxs]
    """

    idx_symb_dct = atom_symbols(gra)

    symb_idx_dct = {}
    for idx, symb in idx_symb_dct.items():
        if symb not in symb_idx_dct:
            symb_idx_dct[symb] = [idx]
        else:
            symb_idx_dct[symb].append(idx)

    return symb_idx_dct
Ejemplo n.º 13
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
Ejemplo n.º 14
0
def terminal_heavy_atom_keys(gra):
    """ terminal heavy atoms, sorted by atom type and hydrogen count
    """
    gra = implicit(gra)
    atm_imp_hyd_vlc_dct = atom_implicit_hydrogen_valences(gra)
    atm_keys = [
        key for key, ngb_keys in atoms_neighbor_atom_keys(gra).items()
        if len(ngb_keys) == 1
    ]
    atm_keys = sorted(atm_keys,
                      key=atm_imp_hyd_vlc_dct.__getitem__,
                      reverse=True)
    atm_symbs = dict_.values_by_key(atom_symbols(gra), atm_keys)
    srt = automol.formula.argsort_symbols(atm_symbs, symbs_first=('C', ))
    atm_keys = tuple(map(atm_keys.__getitem__, srt))
    return atm_keys
Ejemplo n.º 15
0
def inchi_with_sort_from_geometry(gra, geo=None, geo_idx_dct=None):
    """ Generate an InChI string from a molecular graph.
        If coordinates are passed in, they are used to determine stereo.

        :param gra: molecular graph
        :type gra: automol graph data structure
        :param geo: molecular geometry
        :type geo: automol geometry data structure
        :param geo_idx_dct:
        :type geo_idx_dct: dict[:]
        :rtype: (str, tuple(int))
    """
    gra = without_dummy_atoms(gra)
    gra = dominant_resonance(gra)
    atm_keys = sorted(atom_keys(gra))
    bnd_keys = list(bond_keys(gra))
    atm_syms = dict_.values_by_key(atom_symbols(gra), atm_keys)
    atm_bnd_vlcs = dict_.values_by_key(atom_bond_valences(gra), atm_keys)
    atm_rad_vlcs = dict_.values_by_key(atom_unsaturated_valences(gra),
                                       atm_keys)
    bnd_ords = dict_.values_by_key(bond_orders(gra), bnd_keys)

    if geo is not None:
        assert geo_idx_dct is not None
        atm_xyzs = coordinates(geo)
        atm_xyzs = [
            atm_xyzs[geo_idx_dct[atm_key]] if atm_key in geo_idx_dct else
            (0., 0., 0.) for atm_key in atm_keys
        ]
    else:
        atm_xyzs = None

    mlf, key_map_inv = _molfile.from_data(atm_keys,
                                          bnd_keys,
                                          atm_syms,
                                          atm_bnd_vlcs,
                                          atm_rad_vlcs,
                                          bnd_ords,
                                          atm_xyzs=atm_xyzs)
    rdm = _rdkit.from_molfile(mlf)
    ich, aux_info = _rdkit.to_inchi(rdm, with_aux_info=True)
    nums = _parse_sort_order_from_aux_info(aux_info)
    nums = tuple(map(key_map_inv.__getitem__, nums))

    return ich, nums
Ejemplo n.º 16
0
def geometry(gra):
    """ Convert a molecular graph to a molecular geometry.

        :param gra: molecular graph
        :type gra: automol graph data structure
        :rtype: automol molecular geometry data structure
    """

    symbs = atom_symbols(gra)
    if len(symbs) != 1:
        gra = explicit(gra)
        geo = embed_geometry(gra)
    else:
        symb = list(symbs.values())[0]
        # symb = list(symbs.keys())[0]
        geo = ((symb, (0.00, 0.00, 0.00)), )

    return geo
Ejemplo n.º 17
0
def heuristic_bond_distance(gra, key1, key2, angstrom=True, check=False):
    """ heuristic bond distance (in angstroms)
    """
    if check:
        assert key1 in atoms_neighbor_atom_keys(gra)[key2]

    symb_dct = atom_symbols(gra)
    symb1 = symb_dct[key1]
    symb2 = symb_dct[key2]

    if ptab.to_number(symb1) == 1 or ptab.to_number(symb2) == 1:
        dist = XH_DIST
    else:
        dist = XY_DIST

    dist *= 1 if angstrom else phycon.ANG2BOHR

    return dist
Ejemplo n.º 18
0
def equivalent_atoms(gra, atm_key, stereo=True, dummy=True):
    """ Identify sets of isomorphically equivalent atoms

    Two atoms are equivalent if they transform into each other under an
    automorphism

    :param gra: A graph
    :param atm_key: An atom key for the graph
    :param stereo: Consider stereo?
    :type stereo: bool
    :param dummy: Consider dummy atoms?
    :type dummy: bool
    :returns: Keys to equivalent atoms
    :rtype: frozenset
    """
    assert atm_key in atom_keys(gra), ("{} not in {}".format(
        atm_key, atom_keys(gra)))

    atm_symb_dct = atom_symbols(gra)
    atm_ngbs_dct = atoms_neighbor_atom_keys(gra)

    def _neighbor_symbols(key):
        return sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key]))

    # 1. Find atoms with the same symbols
    atm_symb = atm_symb_dct[atm_key]
    cand_keys = atom_keys(gra, sym=atm_symb)

    # 2. Of those, find atoms with the same neighboring atom types
    atm_ngb_symbs = _neighbor_symbols(atm_key)
    cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == atm_ngb_symbs]

    # 3. Find the equivalent atoms from the list of candidates.
    # Strategy: Change the atom symbol to 'Ts' and check for isomorphism.
    # Assumes none of the compounds have element 117.
    ref_gra = set_atom_symbols(gra, {atm_key: 'Ts'})
    atm_keys = []
    for key in cand_keys:
        comp_gra = set_atom_symbols(gra, {key: 'Ts'})
        if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy):
            atm_keys.append(key)

    return frozenset(atm_keys)
Ejemplo n.º 19
0
def _extend_chain_to_include_terminal_hydrogens(gra, keys,
                                                start=True, end=True):
    """ extend each end of a chain to include terminal hydrogens, if any
    """
    symb_dct = atom_symbols(gra)
    atm_ngb_dct = atoms_neighbor_atom_keys(gra)

    sta_ngbs = atm_ngb_dct[keys[0]] - {keys[1]}
    end_ngbs = atm_ngb_dct[keys[-1]] - {keys[-2]}

    sta_ngb = min((k for k in sta_ngbs if symb_dct[k] == 'H'), default=None)
    end_ngb = min((k for k in end_ngbs if symb_dct[k] == 'H'), default=None)

    keys = tuple(keys)

    if start and sta_ngb is not None:
        keys = (sta_ngb,) + keys

    if end and end_ngb is not None:
        keys = keys + (end_ngb,)

    return keys
Ejemplo n.º 20
0
def neighbors_of_type(gra, aidx, symb):
    """ For a given atom, determine the indices of all the atoms
        which neighbor it that are of the type specified.

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param aidx: index of atom for which to find neighbors
        :type aidx: int
        :param symb: symbols of desired atom types for neighbors
        :type symb: str
    """

    idx_symb_dct = atom_symbols(gra)
    neighs = atoms_neighbor_atom_keys(gra)[aidx]
    neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct)

    idxs_of_type = tuple()
    for nidx, nsymb in zip(neighs, neigh_symbs):
        if nsymb == symb:
            idxs_of_type += (nidx, )

    return idxs_of_type
Ejemplo n.º 21
0
def two_bond_idxs(gra, symb1, cent, symb2):
    """ Determine the triplet of indices of atoms of specified
        types that are connected in a chain by two bonds:
        (symb1_idx, cent_idx, symb2_idx).

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param symb1: symbol of atom at one end of chain
        :type symb1: str
        :param cent: symbol of atom in the middle of a chain
        :type cent: str
        :param symb2: symbol of atom at other end of chain
        :type symb2: str
    """

    grps = tuple()

    neigh_dct = atoms_neighbor_atom_keys(gra)
    idx_symb_dct = atom_symbols(gra)
    symb_idx_dct = atom_symbol_idxs(gra)

    cent_idxs = symb_idx_dct.get(cent, tuple())
    for cent_idx in cent_idxs:
        neighs = tuple(neigh_dct[cent_idx])
        neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct)
        if neigh_symbs == (symb1, symb2):
            grp_idxs = (neighs[0], cent_idx, neighs[1])
        elif neigh_symbs == (symb2, symb1):
            grp_idxs = (neighs[1], cent_idx, neighs[0])
        else:
            grp_idxs = ()

        if grp_idxs:
            grps += ((grp_idxs), )

    return grps
Ejemplo n.º 22
0
def complete_branch(gra, key, vma, zma_keys, branch_keys=None):
    """ continue constructing a v-matrix along a chain

    All neighboring atoms along the chain will be included

    Exactly one atom in the chain must already be in the v-matrix

    :param gra: the graph for which the v-matrix will be constructed
    :param vma: a partial v-matrix from which to continue
    :param zma_keys: row keys for the partial v-matrix, identifying the atom
        specified by each row of `vma` in order
    :param branch_keys: optionally, restrict the v-matrix to these keys and
        their neighbors; if `None`, the entire branch will be included
    """
    branch_keys = atom_keys(gra) if branch_keys is None else branch_keys
    keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys)

    zma_keys = list(zma_keys)
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(
        gra, symbs_first=('X', 'C',), symbs_last=('H',), ords_last=(0.1,))

    def _continue(key1, key2, key3, vma, zma_keys):
        k3ns = list(ngb_keys_dct[key3])
        for k3n in set(k3ns) & set(zma_keys):
            k3ns.remove(k3n)

        if k3ns:
            key4 = k3ns.pop(0)

            # Add the leading atom to the v-matrix
            symb = symb_dct[key4]
            key_row = list(map(zma_keys.index, (key3, key2, key1)))
            vma = automol.vmat.add_atom(vma, symb, key_row)
            assert key4 not in zma_keys, ("Atom {:d} already in v-matrix."
                                          .format(key4))
            zma_keys.append(key4)

            # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled
            # from atom 1 for properly decopuled torsions
            for k3n in k3ns:
                sym = symb_dct[k3n]

                if symb_dct[key4] == 'X':
                    key_row = list(map(zma_keys.index, (key3, key4, key2)))
                else:
                    key_row = list(map(zma_keys.index, (key3, key2, key4)))

                vma = automol.vmat.add_atom(vma, sym, key_row)
                assert k3n not in zma_keys, ("Atom {:d} already in v-matrix."
                                             .format(k3n))
                zma_keys.append(k3n)

            # Recursion
            if key4 in branch_keys:
                vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys)

            if symb_dct[key4] == 'X':
                key2 = key4

            for k3n in k3ns:
                if k3n in branch_keys:
                    vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys)

        return vma, zma_keys

    key1, key2, key3 = keys[:3]
    vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys)

    return vma, zma_keys
Ejemplo n.º 23
0
def qualitative_convergence_checker_(gra, keys, rqq_bond_max=1.8,
                                     rqh_bond_max=1.3, rhh_bond_max=1.1,
                                     bond_nobond_diff=0.3):
    """ a convergence checker for error minimization, checking that the
    geometry is qualitatively correct (correct connectivity and stereo)
    """
    symb_dct = atom_symbols(gra)
    pairs = set(map(frozenset, itertools.combinations(keys, 2)))

    bnd_keys = pairs & bond_keys(gra)
    nob_keys = pairs - bond_keys(gra)

    nob_symbs = tuple(tuple(map(symb_dct.__getitem__, nob_key))
                      for nob_key in nob_keys)
    bnd_symbs = tuple(tuple(map(symb_dct.__getitem__, bnd_key))
                      for bnd_key in bnd_keys)
    nob_idxs = tuple(tuple(map(keys.index, nob_key)) for nob_key in nob_keys)
    bnd_idxs = tuple(tuple(map(keys.index, bnd_key)) for bnd_key in bnd_keys)

    bnd_udists = tuple((rqq_bond_max if 'H' not in symb else
                        rhh_bond_max if set(symb) == {'H'} else
                        rqh_bond_max) for symb in bnd_symbs)

    diff = bond_nobond_diff
    nob_ldists = tuple((rqq_bond_max+diff if 'H' not in symb else
                        rhh_bond_max+diff if set(symb) == {'H'} else
                        rqh_bond_max+diff) for symb in nob_symbs)

    bnd_idxs += tuple(map(tuple, map(reversed, bnd_idxs)))
    bnd_idx_vecs = tuple(map(list, zip(*bnd_idxs)))
    bnd_udists *= 2

    nob_idxs += tuple(map(tuple, map(reversed, nob_idxs)))
    nob_idx_vecs = tuple(map(list, zip(*nob_idxs)))
    nob_ldists *= 2

    symbs = tuple(map(symb_dct.__getitem__, keys))
    geo_idx_dct = dict(map(reversed, enumerate(keys)))
    atm_ste_keys = atom_stereo_keys(gra) & set(keys)
    bnd_ste_keys = bond_stereo_keys(gra) & bnd_keys
    atm_ste_par_dct = atom_stereo_parities(gra)
    bnd_ste_par_dct = bond_stereo_parities(gra)

    def _is_converged(xmat, err, grad):
        assert err and numpy.any(grad)
        xyzs = xmat[:, :3]
        dmat = embed.distance_matrix_from_coordinates(xyzs)

        # check for correct connectivity
        connectivity_check = (
            (numpy.all(dmat[bnd_idx_vecs] < bnd_udists)
             if bnd_udists else True) and
            (numpy.all(dmat[nob_idx_vecs] > nob_ldists)
             if nob_ldists else True))

        # check for correct stereo parities
        geo = _create.geom.from_data(symbs, xyzs, angstrom=True)
        atom_stereo_check = all(
            (_atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct)
             == atm_ste_par_dct[atm_key])
            for atm_key in atm_ste_keys)

        bond_stereo_check = all(
            (_bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct)
             == bnd_ste_par_dct[bnd_key])
            for bnd_key in bnd_ste_keys)

        return connectivity_check and atom_stereo_check and bond_stereo_check

    return _is_converged
Ejemplo n.º 24
0
def complete_branch(gra, key, vma, zma_keys, branch_keys=None):
    """ continue constructing a v-matrix along a chain

    All neighboring atoms along the chain will be included

    Exactly one atom in the chain must already be in the v-matrix

    :param gra: the graph for which the v-matrix will be constructed
    :param keys: the keys for atoms along the chain, which must be contiguous;
        the first atom must already appear in the v-matrix
    :param vma: a partial v-matrix from which to continue
    :param zma_keys: row keys for the partial v-matrix, identifying the atom
        specified by each row of `vma` in order
    :param branch_keys: optionally, restrict the v-matrix to these keys and
        their neighbors; if `None`, the entire branch will be included
    """
    branch_keys = atom_keys(gra) if branch_keys is None else branch_keys
    keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys)

    zma_keys = list(zma_keys)
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ),
                                                   prioritize_keys=branch_keys)

    # If this z-matrix is being continued from a partial z-matrix, the leading
    # atom for a torsion may have already be defined. To handle this case, I
    # make a dictionary of these leading atoms and use them below where needed.
    lead_key_dct = {}
    for idx, key_row in enumerate(automol.vmat.key_matrix(vma)):
        axis = key_row[:2]
        if None not in axis:
            axis = tuple(map(zma_keys.__getitem__, axis))
            if axis not in lead_key_dct:
                lead_key_dct[axis] = zma_keys[idx]

    def _continue(key1, key2, key3, vma, zma_keys):
        k3ns = list(ngb_keys_dct[key3])
        for k3n in set(k3ns) & set(zma_keys):
            k3ns.remove(k3n)

        if k3ns:
            key4 = k3ns.pop(0)

            lead_key = None
            if (key3, key2) in lead_key_dct:
                lead_key = lead_key_dct[(key3, key2)]

            # Add the leading atom to the v-matrix
            symb = symb_dct[key4]
            dkey = key1 if lead_key is None else lead_key
            key_row = list(map(zma_keys.index, (key3, key2, dkey)))
            vma = automol.vmat.add_atom(vma, symb, key_row)
            assert key4 not in zma_keys, (
                "Atom {:d} already in v-matrix.".format(key4))
            zma_keys.append(key4)

            dkey = key4 if lead_key is None else lead_key
            # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled
            # from atom 1 for properly decopuled torsions
            for k3n in k3ns:
                sym = symb_dct[k3n]

                if symb_dct[dkey] == 'X':
                    key_row = list(map(zma_keys.index, (key3, dkey, key2)))
                else:
                    key_row = list(map(zma_keys.index, (key3, key2, dkey)))

                vma = automol.vmat.add_atom(vma, sym, key_row)
                assert k3n not in zma_keys, (
                    "Atom {:d} already in v-matrix.".format(k3n))
                zma_keys.append(k3n)

            # Recursion
            if key4 in branch_keys:
                vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys)

            if symb_dct[key4] == 'X':
                key2 = key4

            for k3n in k3ns:
                if k3n in branch_keys:
                    vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys)

        return vma, zma_keys

    key1, key2, key3 = keys[:3]
    vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys)

    return vma, zma_keys
Ejemplo n.º 25
0
def electron_count(gra, charge=0):
    """ the number of electrons in the molecule
    """
    atm_symb_dct = atom_symbols(explicit(gra))
    nelec = sum(map(ptab.to_number, atm_symb_dct.values())) - charge
    return nelec