Ejemplo n.º 1
0
def is_stereo_compatible(tra, sgr1, sgr2):
    """ is this transformation compatible with the reactant/product stereo
    assignments?
    """
    cgr1 = without_stereo_parities(sgr1)
    cgr2 = without_stereo_parities(sgr2)
    atm_key_dct = full_isomorphism(apply(tra, cgr1), cgr2)

    # determine the stereo centers which are preserved in the transformation
    sgr1 = _relabel(sgr1, atm_key_dct)
    atm_keys = sorted(atom_stereo_keys(sgr1) & atom_stereo_keys(sgr2))
    bnd_keys = sorted(bond_stereo_keys(sgr1) & bond_stereo_keys(sgr2))

    atm_pars1 = dict_.values_by_key(atom_stereo_parities(sgr1), atm_keys)
    atm_pars2 = dict_.values_by_key(atom_stereo_parities(sgr2), atm_keys)
    bnd_pars1 = dict_.values_by_key(bond_stereo_parities(sgr1), bnd_keys)
    bnd_pars2 = dict_.values_by_key(bond_stereo_parities(sgr2), bnd_keys)

    atm_ngb_keys_dct1 = atom_neighbor_keys(sgr1)
    atm_ngb_keys_dct2 = atom_neighbor_keys(sgr2)

    ret = True

    for atm_key, par1, par2 in zip(atm_keys, atm_pars1, atm_pars2):
        atm_ngb_keys1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm_key, atm_ngb_keys_dct1[atm_key])
        atm_ngb_keys2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm_key, atm_ngb_keys_dct2[atm_key])

        if _permutation_parity(atm_ngb_keys1, atm_ngb_keys2):
            ret &= (par1 == par2)
        else:
            ret &= (par1 != par2)

    for bnd_key, par1, par2 in zip(bnd_keys, bnd_pars1, bnd_pars2):
        atm1_key, atm2_key = bnd_key

        atm1_ngb_key1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm1_key, atm_ngb_keys_dct1[atm1_key] - {atm2_key})[0]
        atm2_ngb_key1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm2_key, atm_ngb_keys_dct1[atm2_key] - {atm1_key})[0]
        atm1_ngb_key2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm1_key, atm_ngb_keys_dct2[atm1_key] - {atm2_key})[0]
        atm2_ngb_key2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm2_key, atm_ngb_keys_dct2[atm2_key] - {atm1_key})[0]

        if not ((atm1_ngb_key1 != atm1_ngb_key2) ^
                (atm2_ngb_key1 != atm2_ngb_key2)):
            ret &= (par1 == par2)
        else:
            ret &= (par1 != par2)

    return ret
Ejemplo n.º 2
0
def _bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct):
    """ get the current stereo parity of a bond from its geometry
    """
    atm1_key, atm2_key = bnd_key
    atm_ngb_keys_dct = atom_neighbor_keys(gra)
    atm1_ngb_keys = atm_ngb_keys_dct[atm1_key] - {atm2_key}
    atm2_ngb_keys = atm_ngb_keys_dct[atm2_key] - {atm1_key}

    atm1_ngb_keys = stereo_sorted_atom_neighbor_keys(gra, atm1_key,
                                                     atm1_ngb_keys)
    atm2_ngb_keys = stereo_sorted_atom_neighbor_keys(gra, atm2_key,
                                                     atm2_ngb_keys)

    # get the top priority neighbor keys on each side
    atm1_ngb_key = atm1_ngb_keys[0]
    atm2_ngb_key = atm2_ngb_keys[0]

    # determine the parity based on the coordinates
    xyzs = automol.geom.coordinates(geo)
    atm1_xyz = xyzs[geo_idx_dct[atm1_key]]
    atm2_xyz = xyzs[geo_idx_dct[atm2_key]]
    atm1_ngb_xyz = xyzs[geo_idx_dct[atm1_ngb_key]]
    atm2_ngb_xyz = xyzs[geo_idx_dct[atm2_ngb_key]]
    atm1_bnd_vec = numpy.subtract(atm1_ngb_xyz, atm1_xyz)
    atm2_bnd_vec = numpy.subtract(atm2_ngb_xyz, atm2_xyz)
    dot_val = numpy.vdot(atm1_bnd_vec, atm2_bnd_vec)
    assert dot_val != 0.  # for now, assume no collinear
    par = dot_val > 0.
    return par
Ejemplo n.º 3
0
def two_bond_idxs(gra, asymb1='H', cent='C', asymb2='H'):
    """ Determine triplet of idxs for describing bond

        idxs = (asymb1_idx, cent_idx, asymb2_idx)
    """

    grps = tuple()

    neigh_dct = atom_neighbor_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 == (asymb1, asymb2):
            grp_idxs = (neighs[0], cent_idx, neighs[1])
        elif neigh_symbs == (asymb2, asymb1):
            grp_idxs = (neighs[1], cent_idx, neighs[0])
        else:
            grp_idxs = ()

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

    return grps
Ejemplo n.º 4
0
def isomorphic_radical_graphs(gra):
    """ Generate a set of graphs that are isomorphic to a graph
        of a radical species
    """

    # 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

    iso_gras = []
    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 = atom_neighbor_keys(new_graph)
            for neigh in neighbors[aidx]:
                if symbols[neigh] == 'H':
                    aneighbor = neigh
                    break
            new_graph = remove_atoms(new_graph, [aneighbor])

            # Test to see if new radical species is the same as the original
            inv_atm_key_dct = full_isomorphism(gra, new_graph)
            if inv_atm_key_dct:
                iso_gras.append(new_graph)

    return iso_gras
Ejemplo n.º 5
0
def _dihedral_increment(sgr, atm1_key, atm2_key, atm3_key, check=False):
    """ predict dihedral increment for atoms attached to `atm3_key`
    """
    if check:
        atm_ngb_keys_dct = atom_neighbor_keys(sgr)
        atm2_ngb_keys = atm_ngb_keys_dct[atm2_key]
        atm3_ngb_keys = atm_ngb_keys_dct[atm3_key]
        assert atm2_key in atm3_ngb_keys
        assert atm1_key in atm2_ngb_keys - {atm3_key}

    atm_hyb_dct = resonance_dominant_atom_hybridizations(sgr)
    atm3_hyb = atm_hyb_dct[atm3_key]
    dih_incr = 2 * numpy.pi / atm3_hyb if not atm3_hyb == 0 else 0.
    return dih_incr
Ejemplo n.º 6
0
def neighbors_of_type(gra, aidx, asymb='H'):
    """ Get the neighbor indices for a certain type
    """

    idx_symb_dct = atom_symbols(gra)
    neighs = atom_neighbor_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 == asymb:
            idxs_of_type += (nidx,)

    return idxs_of_type
Ejemplo n.º 7
0
def stereo_priority_vector(gra, atm_key, atm_ngb_key):
    """ generates a sortable one-to-one representation of the branch extending
    from `atm_key` through its bonded neighbor `atm_ngb_key`
    """
    bbn_keys = backbone_keys(gra)
    exp_hyd_keys = explicit_hydrogen_keys(gra)

    if atm_ngb_key not in bbn_keys:
        assert atm_ngb_key in exp_hyd_keys
        assert frozenset({atm_key, atm_ngb_key}) in bonds(gra)
        pri_vec = ()
    else:
        gra = implicit(gra)
        atm_dct = atoms(gra)
        bnd_dct = bonds(gra)
        assert atm_key in bbn_keys
        assert frozenset({atm_key, atm_ngb_key}) in bnd_dct

        # here, switch to an implicit graph
        atm_ngb_keys_dct = atom_neighbor_keys(gra)

        def _priority_vector(atm1_key, atm2_key, seen_keys):
            # we keep a list of seen keys to cut off cycles, avoiding infinite
            # loops

            bnd_val = bnd_dct[frozenset({atm1_key, atm2_key})]
            atm_val = atm_dct[atm2_key]

            bnd_val = _replace_nones_with_negative_infinity(bnd_val)
            atm_val = _replace_nones_with_negative_infinity(atm_val)

            if atm2_key in seen_keys:
                ret = (bnd_val,)
            else:
                seen_keys.update({atm1_key, atm2_key})
                atm3_keys = atm_ngb_keys_dct[atm2_key] - {atm1_key}
                if atm3_keys:
                    next_vals, seen_keys = zip(*[
                        _priority_vector(atm2_key, atm3_key, seen_keys)
                        for atm3_key in atm3_keys])
                    ret = (bnd_val, atm_val) + next_vals
                else:
                    ret = (bnd_val, atm_val)

            return ret, seen_keys

        pri_vec, _ = _priority_vector(atm_key, atm_ngb_key, set())

    return pri_vec
Ejemplo n.º 8
0
def _atom_stereo_corrected_geometry(gra, atm_ste_par_dct, geo, geo_idx_dct):
    """ correct the atom stereo parities of a geometry, for a subset of atoms
    """
    ring_atm_keys = set(itertools.chain(*rings_atom_keys(gra)))
    atm_ngb_keys_dct = atom_neighbor_keys(gra)

    atm_keys = list(atm_ste_par_dct.keys())
    for atm_key in atm_keys:
        par = atm_ste_par_dct[atm_key]
        curr_par = _atom_stereo_parity_from_geometry(gra, atm_key, geo,
                                                     geo_idx_dct)

        if curr_par != par:
            atm_ngb_keys = atm_ngb_keys_dct[atm_key]
            # for now, we simply exclude rings from the pivot keys
            # (will not work for stereo atom at the intersection of two rings)
            atm_piv_keys = list(atm_ngb_keys - ring_atm_keys)[:2]
            assert len(atm_piv_keys) == 2
            atm3_key, atm4_key = atm_piv_keys

            # get coordinates
            xyzs = automol.geom.coordinates(geo)
            atm_xyz = xyzs[geo_idx_dct[atm_key]]
            atm3_xyz = xyzs[geo_idx_dct[atm3_key]]
            atm4_xyz = xyzs[geo_idx_dct[atm4_key]]

            # do the rotation
            rot_axis = cart.vec.unit_bisector(atm3_xyz,
                                              atm4_xyz,
                                              orig_xyz=atm_xyz)

            rot_atm_keys = (
                atom_keys(branch(gra, atm_key, {atm_key, atm3_key}))
                | atom_keys(branch(gra, atm_key, {atm_key, atm4_key})))
            rot_idxs = list(map(geo_idx_dct.__getitem__, rot_atm_keys))

            geo = automol.geom.rotate(geo,
                                      rot_axis,
                                      numpy.pi,
                                      orig_xyz=atm_xyz,
                                      idxs=rot_idxs)

        assert _atom_stereo_parity_from_geometry(gra, atm_key, geo,
                                                 geo_idx_dct) == par
        gra = set_atom_stereo_parities(gra, {atm_key: par})

    return geo, gra
Ejemplo n.º 9
0
def _bond_distance(gra, atm1_key, atm2_key, check=True):
    """ predicted bond distance

    (currently crude, but could easily be made more sophisticated
    """
    atm_sym_dct = atom_symbols(gra)

    if check:
        assert atm2_key in atom_neighbor_keys(gra)[atm1_key]

    atm1_sym = atm_sym_dct[atm1_key]
    atm2_sym = atm_sym_dct[atm2_key]

    if atm1_sym == 'H' or atm2_sym == 'H':
        dist = 1.1 * qcc.conversion_factor('angstrom', 'bohr')
    else:
        dist = 1.5 * qcc.conversion_factor('angstrom', 'bohr')

    return dist
Ejemplo n.º 10
0
def _atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct):
    """ get the current stereo parity of an atom from its geometry
    """
    atm_ngb_keys_dct = atom_neighbor_keys(gra)
    atm_ngb_keys = atm_ngb_keys_dct[atm_key]

    # sort the neighbor keys by stereo priority
    atm_ngb_keys = stereo_sorted_atom_neighbor_keys(gra, atm_key, atm_ngb_keys)

    # determine the parity based on the coordinates
    xyzs = automol.geom.coordinates(geo)
    atm_ngb_idxs = dict_.values_by_key(geo_idx_dct, atm_ngb_keys)
    atm_ngb_xyzs = [xyzs[idx] for idx in atm_ngb_idxs]
    det_mat = numpy.ones((4, 4))
    det_mat[:, :3] = atm_ngb_xyzs
    det_val = numpy.linalg.det(det_mat)
    assert det_val != 0.  # for now, assume no four-atom planes
    par = det_val > 0.
    return par
Ejemplo n.º 11
0
def stereogenic_bond_keys(gra):
    """ (unassigned) stereogenic bonds in this graph
    """
    gra = without_bond_orders(gra)
    gra = explicit(gra)  # for simplicity, add the explicit hydrogens back in
    bnd_keys = dict_.keys_by_value(
        resonance_dominant_bond_orders(gra), lambda x: 2 in x)

    # make sure both ends are sp^2 (excludes cumulenes)
    atm_hyb_dct = resonance_dominant_atom_hybridizations(gra)
    sp2_atm_keys = dict_.keys_by_value(atm_hyb_dct, lambda x: x == 2)
    bnd_keys = frozenset({bnd_key for bnd_key in bnd_keys
                          if bnd_key <= sp2_atm_keys})

    bnd_keys -= bond_stereo_keys(gra)
    bnd_keys -= functools.reduce(  # remove double bonds in small rings
        frozenset.union,
        filter(lambda x: len(x) < 8, rings_bond_keys(gra)), frozenset())

    atm_ngb_keys_dct = atom_neighbor_keys(gra)

    def _is_stereogenic(bnd_key):
        atm1_key, atm2_key = bnd_key

        def _is_symmetric_on_bond(atm_key, atm_ngb_key):
            atm_ngb_keys = list(atm_ngb_keys_dct[atm_key] - {atm_ngb_key})

            if not atm_ngb_keys:                # C=:O:
                ret = True
            elif len(atm_ngb_keys) == 1:        # C=N:-X
                ret = False
            else:
                assert len(atm_ngb_keys) == 2   # C=C(-X)-Y
                ret = (stereo_priority_vector(gra, atm_key, atm_ngb_keys[0]) ==
                       stereo_priority_vector(gra, atm_key, atm_ngb_keys[1]))

            return ret

        return not (_is_symmetric_on_bond(atm1_key, atm2_key) or
                    _is_symmetric_on_bond(atm2_key, atm1_key))

    ste_gen_bnd_keys = frozenset(filter(_is_stereogenic, bnd_keys))
    return ste_gen_bnd_keys
Ejemplo n.º 12
0
def stereogenic_atom_keys(gra):
    """ (unassigned) stereogenic atoms in this graph
    """
    gra = without_bond_orders(gra)
    gra = explicit(gra)  # for simplicity, add the explicit hydrogens back in
    atm_keys = dict_.keys_by_value(atom_bond_valences(gra), lambda x: x == 4)
    atm_keys -= atom_stereo_keys(gra)

    atm_ngb_keys_dct = atom_neighbor_keys(gra)

    def _is_stereogenic(atm_key):
        atm_ngb_keys = list(atm_ngb_keys_dct[atm_key])
        pri_vecs = [stereo_priority_vector(gra, atm_key, atm_ngb_key)
                    for atm_ngb_key in atm_ngb_keys]
        return not any(pv1 == pv2
                       for pv1, pv2 in itertools.combinations(pri_vecs, r=2))

    ste_gen_atm_keys = frozenset(filter(_is_stereogenic, atm_keys))
    return ste_gen_atm_keys
Ejemplo n.º 13
0
def prod_beta_scission(gra):
    """ products of beta scission
    """

    prod_gras = tuple()

    rad_idxs = resonance_dominant_radical_atom_keys(gra)
    single_bonds = bonds_of_order(gra, mbond=1)

    for rad_idx in rad_idxs:
        rad_neighs = atom_neighbor_keys(gra)[rad_idx]
        for single_bond in single_bonds:
            bond = frozenset(single_bond)
            if rad_neighs & bond and rad_idx not in bond:
                gra2 = remove_bonds(gra, [bond])
                disconn_gras = automol.graph.connected_components(gra2)
                prod_gras += (disconn_gras, )

    return _unique_gras(prod_gras)
Ejemplo n.º 14
0
def _bond_angle(sgr, atm1_key, atm2_key, atm3_key, check=True):
    """ predict the bond angles an atom makes with its neighbors
    """
    if check:
        atm_ngb_keys_dct = atom_neighbor_keys(sgr)
        atm2_ngb_keys = atm_ngb_keys_dct[atm2_key]
        assert {atm1_key, atm3_key} <= atm2_ngb_keys

    atm_hyb_dct = resonance_dominant_atom_hybridizations(sgr)
    atm2_hyb = atm_hyb_dct[atm2_key]

    if atm2_hyb == 3:
        ang_deg = 109.5
    elif atm2_hyb == 2:
        ang_deg = 120.0
    else:
        assert atm2_hyb == 1
        ang_deg = 180.0

    ang = ang_deg * qcc.conversion_factor('degree', 'radian')
    return ang
Ejemplo n.º 15
0
def _complete_zmatrix_for_branch(gra, atm1_key, atm2_key, atm3_key, zma,
                                 zma_key_dct):
    """ core function for generating geometries; starting from three
    neighboring atoms at the end of the z-matrix, fills out the z-matrix for
    the rest of the branch extending out from the third atom

    returns a z-matrix and a dictionary mapping atom keys to rows of the
    z-matrix
    """
    atm_sym_dct = atom_symbols(gra)
    atm_ngb_keys_dct = atom_neighbor_keys(gra)

    atm1_zma_key = zma_key_dct[atm1_key]
    atm2_zma_key = zma_key_dct[atm2_key]
    atm3_zma_key = zma_key_dct[atm3_key]

    atm4_keys = atm_ngb_keys_dct[atm3_key] - {atm2_key}

    # first handle the linear bond case, inserting a dummy atom
    atm_hyb = resonance_dominant_atom_hybridizations(gra)[atm3_key]
    if atm_hyb == 1 and atm4_keys:
        assert len(atm4_keys) == 1
        atm4_key, = atm4_keys

        # first, insert the dummy atom
        dist4_name = automol.zmatrix.new_distance_name(zma)
        dist4_val = 1.

        ang4_name = automol.zmatrix.new_central_angle_name(zma)
        ang4_val = RIT_ANG

        dih4_name = automol.zmatrix.new_dihedral_angle_name(zma)
        dih4_val = 0.

        gra, dummy_atm_key = add_bonded_atom(gra, 'X', atm3_key, bnd_ord=0)
        dummy_atm_zma_key = automol.zmatrix.count(zma)

        zma_key_dct[dummy_atm_key] = dummy_atm_zma_key

        zma = automol.zmatrix.append(
            zma, 'X', [atm3_zma_key, atm2_zma_key, atm1_zma_key],
            [dist4_name, ang4_name, dih4_name],
            [dist4_val, ang4_val, dih4_val])

        # shift the keys to include the dummy atom
        atm1_key, atm2_key = atm2_key, dummy_atm_key
        atm1_zma_key, atm2_zma_key = atm2_zma_key, dummy_atm_zma_key

        atm4_sym = atm_sym_dct[atm4_key]

        dist4_name = automol.zmatrix.new_distance_name(zma)
        dist4_val = _bond_distance(gra, atm3_key, atm4_key)

        ang4_name = automol.zmatrix.new_central_angle_name(zma)
        ang4_val = RIT_ANG

        dih4_name = automol.zmatrix.new_dihedral_angle_name(zma)
        dih4_val = LIN_ANG

        zma_key_dct[atm4_key] = automol.zmatrix.count(zma)

        zma = automol.zmatrix.append(
            zma, atm4_sym, [atm3_zma_key, atm2_zma_key, atm1_zma_key],
            [dist4_name, ang4_name, dih4_name],
            [dist4_val, ang4_val, dih4_val])

        geo = automol.zmatrix.geometry(zma)

        # recursion 1
        zma, zma_key_dct, gra = _complete_zmatrix_for_branch(
            gra, atm2_key, atm3_key, atm4_key, zma, zma_key_dct)

    # from here on, we can assume a non-linear bond
    else:
        dih_incr = _dihedral_increment(gra, atm1_key, atm2_key, atm3_key)

        fixed_atm4_keys = set(atm4_keys) & set(zma_key_dct)
        if fixed_atm4_keys:
            geo = automol.zmatrix.geometry(zma)
            atm4_zma_keys = list(map(zma_key_dct.__getitem__, fixed_atm4_keys))
            dih4_vals = [
                automol.geom.dihedral_angle(geo, atm1_zma_key, atm2_zma_key,
                                            atm3_zma_key, atm4_zma_key)
                for atm4_zma_key in atm4_zma_keys
            ]
            dih4_val = max(dih4_vals)
        else:
            dih4_val = numpy.pi - dih_incr

        # subtract off the fixed atom 4 keys in case we're dealing with a ring
        for atm4_key in atm4_keys - fixed_atm4_keys:
            atm4_sym = atm_sym_dct[atm4_key]

            dist4_name = automol.zmatrix.new_distance_name(zma)
            dist4_val = _bond_distance(gra, atm3_key, atm4_key)

            ang4_name = automol.zmatrix.new_central_angle_name(zma)
            ang4_val = _bond_angle(gra, atm2_key, atm3_key, atm4_key)

            dih4_name = automol.zmatrix.new_dihedral_angle_name(zma)

            dih4_val += dih_incr
            dih4_val = numpy.mod(dih4_val, 2 * numpy.pi)

            zma_key_dct[atm4_key] = automol.zmatrix.count(zma)

            zma = automol.zmatrix.append(
                zma, atm4_sym, [atm3_zma_key, atm2_zma_key, atm1_zma_key],
                [dist4_name, ang4_name, dih4_name],
                [dist4_val, ang4_val, dih4_val])

            # recursion 2
            zma, zma_key_dct, gra = _complete_zmatrix_for_branch(
                gra, atm2_key, atm3_key, atm4_key, zma, zma_key_dct)

    gra_with_dummies = gra

    return zma, zma_key_dct, gra_with_dummies
Ejemplo n.º 16
0
def _start_zmatrix_from_atom(gra, atm_key):
    """ generates a z-matrix for a single atom and its neighbors

    returns a z-matrix and a dictionary mapping atom keys to rows of the
    z-matrix
    """
    atm_ngb_keys_dct = atom_neighbor_keys(gra)

    dummy_atm_key = None

    atm_sym_dct = atom_symbols(gra)

    # sort hydrogens to be first
    atm_ngb_keys = sorted(atm_ngb_keys_dct[atm_key])
    atm_ngb_syms = list(map(atm_sym_dct.__getitem__, atm_ngb_keys))
    srt = formula.argsort_symbols(atm_ngb_syms, syms=('H', 'C'))
    atm_ngb_keys = list(map(atm_ngb_keys.__getitem__, srt))

    atm_keys = [atm_key] + atm_ngb_keys

    zma_key_dct = {
        atm_key: zma_key
        for zma_key, atm_key in enumerate(atm_keys)
    }

    syms = list(map(atm_sym_dct.__getitem__, atm_keys))

    natms = len(atm_keys)

    key_mat = [[None, None, None], [0, None, None], [0, 1, None], [0, 1, 2],
               [0, 1, 2]][:natms]

    name_mat = [[None, None, None], ['R1', None, None], ['R2', 'A2', None],
                ['R3', 'A3', 'D3'], ['R4', 'A4', 'D4']][:natms]

    atm_hyb = resonance_dominant_atom_hybridizations(gra)[atm_key]

    # z-matrix coordinate values
    val_dct = {}

    # determine bond distances
    for dist_name, atm_ngb_key in zip(['R1', 'R2', 'R3', 'R4'], atm_ngb_keys):
        dist_val = _bond_distance(gra, atm_key, atm_ngb_key)
        val_dct[dist_name] = dist_val

    # determine bond angles and dihedral angles
    if atm_hyb == 3:
        # for sp3 atoms, use z-matrix for a tetrahedral structure
        val_dct.update({
            'A2': TET_ANG,
            'A3': TET_ANG,
            'A4': TET_ANG,
            'D3': +TRI_ANG,
            'D4': -TRI_ANG
        })
    elif atm_hyb == 2:
        # for sp2 atoms, use z-matrix for a trigonal planar structure
        val_dct.update({'A2': TRI_ANG, 'A3': TRI_ANG, 'D3': LIN_ANG})
    elif atm_hyb == 1 and natms > 2:
        # for sp1 atoms, uze z-matrix for a linear structure

        # if there's only one neighbor, we don't need to set any angles at all
        assert natms == 3

        # insert a dummy atom
        syms.insert(2, 'X')
        key_mat.append([0, 2, 1])
        name_mat.append(['R3', 'A3', 'D3'])

        # note: we need to insert the dummy atom bond distance at R2 and shift
        # over the current value
        val_dct.update({
            'R2': 1.0,
            'R3': val_dct['R2'],
            'A2': RIT_ANG,
            'A3': RIT_ANG,
            'D3': LIN_ANG
        })

        gra, dummy_atm_key = add_bonded_atom(gra, 'X', atm_key, bnd_ord=0)

        zma_key_dct[dummy_atm_key] = 2
        zma_key_dct[atm_keys[2]] = 3

    vma = automol.vmatrix.from_data(syms, key_mat, name_mat)

    names = automol.vmatrix.names(vma)
    val_dct = {name: val_dct[name] for name in names}

    zma = automol.zmatrix.from_data(syms, key_mat, name_mat, val_dct)

    gra_with_dummies = gra
    return zma, zma_key_dct, dummy_atm_key, gra_with_dummies