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
def _start_zmatrix_from_ring(gra, rng_atm_keys): """ generates a z-matrix for a ring """ # the key dictionary can be constructed immediately zma_key_dct = { atm_key: zma_key for zma_key, atm_key in enumerate(rng_atm_keys) } # now, build the z-matrix natms = len(rng_atm_keys) atm_sym_dct = atom_symbols(gra) dist_val = 1.5 * qcc.conversion_factor('angstrom', 'bohr') ang_val = (natms - 2.) * numpy.pi / natms dih_val = 0. # 1. construct the z-matrix for a 3-atom system key_mat = [[None, None, None], [0, None, None], [1, 0, None]] name_mat = [[None, None, None], ['R1', None, None], ['R2', 'A2', None]] syms = dict_.values_by_key(atm_sym_dct, rng_atm_keys[:3]) val_dct = {'R1': dist_val, 'R2': dist_val, 'A2': ang_val} zma = automol.zmatrix.from_data(syms, key_mat, name_mat, val_dct) # 2. append z-matrix rows for the remaining atoms for row, rng_atm_key in enumerate(rng_atm_keys): if row > 2: sym = atm_sym_dct[rng_atm_key] dist_name = automol.zmatrix.new_distance_name(zma) ang_name = automol.zmatrix.new_central_angle_name(zma) dih_name = automol.zmatrix.new_dihedral_angle_name(zma) zma = automol.zmatrix.append(zma, sym, [row - 1, row - 2, row - 3], [dist_name, ang_name, dih_name], [dist_val, ang_val, dih_val]) return zma, zma_key_dct
def connected_heuristic_zmatrix(gra): """ stereo-specific coordinates for a connected molecular graph (currently unable to handle rings -- fix that) """ assert gra == explicit(gra) atm_sym_dct = atom_symbols(gra) # this will contain triplets of adjacent atoms from which to continue # filling out the z-matrix, after it has been started triplets = [] # 1. start the z-matrix and set the lists of triplets rng_atm_keys_lst = rings_atom_keys(gra) if not rng_atm_keys_lst: # find the first heavy atom in the longest chain (if there isn't one, # we are dealing with atomic or molecular hydrogen, which will be # captured by the last two cases) chain = longest_chain(gra) if atm_sym_dct[chain[0]] != 'H': chain = list(reversed(chain)) if len(chain) > 1: atm_key = chain[1] else: atm_key = chain[0] # determine the z-matrix of the starting atom and its neighbors zma, zma_key_dct, dummy_atm_key, gra = _start_zmatrix_from_atom( gra, atm_key) # since this is the first heavy atom in the longest chain, we only need # to follow one branch from this atom to complete the z-matrix; this # will be the branch extending toward the next heavy atom in the chai if len(chain) > 3: atm1_key, atm2_key, atm3_key = chain[:3] # if we inserted a dummy atom on the starting geometry, we should # use that as atom 1 in the triplet, rather than if dummy_atm_key is not None: atm1_key = dummy_atm_key triplets = [(atm1_key, atm2_key, atm3_key)] elif len(rng_atm_keys_lst) == 1: rng_atm_keys, = rng_atm_keys_lst zma, zma_key_dct = _start_zmatrix_from_ring(gra, rng_atm_keys) triplets += list(mit.windowed(rng_atm_keys[-2:] + rng_atm_keys, 3)) else: # currently, multiple rings are not implemented raise NotImplementedError # 2. complete the z-matrix by looping over triplets for atm1_key, atm2_key, atm3_key in triplets: zma, zma_key_dct, gra = _complete_zmatrix_for_branch( gra, atm1_key, atm2_key, atm3_key, zma, zma_key_dct) # 3. convert to Cartesian geometry for stereo correction geo = automol.zmatrix.geometry(zma) geo_idx_dct = zma_key_dct geo = _stereo_corrected_geometry(gra, geo, geo_idx_dct) # 4. convert back to z-matrix, keeping the original z-matrix structure vma = automol.zmatrix.var_(zma) zma = automol.zmatrix.from_geometry(vma, geo) return zma, zma_key_dct
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
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