def add_bonds(gra, keys, ord_dct=None, ste_par_dct=None, check=True): """ add bonds to this molecular graph """ bnd_keys = set(bond_keys(gra)) bnd_ord_dct = bond_orders(gra) bnd_ste_par_dct = bond_stereo_parities(gra) keys = set(map(frozenset, keys)) ord_dct = {} if ord_dct is None else ord_dct ste_par_dct = {} if ste_par_dct is None else ste_par_dct ord_dct = dict_.transform_keys(ord_dct, frozenset) ste_par_dct = dict_.transform_keys(ste_par_dct, frozenset) if check: assert not keys & bnd_keys, ( '{} and {} have a non-empty intersection'.format(keys, bnd_keys)) assert set(ord_dct.keys()) <= keys assert set(ste_par_dct.keys()) <= keys bnd_keys.update(keys) bnd_ord_dct.update(ord_dct) bnd_ste_par_dct.update(ste_par_dct) atm_dct = atoms(gra) bnd_dct = bonds_from_data(bnd_keys=bnd_keys, bnd_ord_dct=bnd_ord_dct, bnd_ste_par_dct=bnd_ste_par_dct) gra = from_atoms_and_bonds(atm_dct=atm_dct, bnd_dct=bnd_dct) return gra
def yaml_dictionary(gra, one_indexed=True): """ generate a YAML dictionary representing a given graph """ if one_indexed: # shift to one-indexing when we print atm_key_dct = {atm_key: atm_key + 1 for atm_key in atom_keys(gra)} gra = relabel(gra, atm_key_dct) yaml_atm_dct = atoms(gra) yaml_bnd_dct = bonds(gra) # prepare the atom dictionary yaml_atm_dct = dict(sorted(yaml_atm_dct.items())) yaml_atm_dct = dict_.transform_values( yaml_atm_dct, lambda x: dict(zip(ATM_PROP_NAMES, x))) # perpare the bond dictionary yaml_bnd_dct = dict_.transform_keys(yaml_bnd_dct, lambda x: tuple(sorted(x))) yaml_bnd_dct = dict(sorted(yaml_bnd_dct.items())) yaml_bnd_dct = dict_.transform_keys(yaml_bnd_dct, lambda x: '-'.join(map(str, x))) yaml_bnd_dct = dict_.transform_values( yaml_bnd_dct, lambda x: dict(zip(BND_PROP_NAMES, x))) yaml_gra_dct = {'atoms': yaml_atm_dct, 'bonds': yaml_bnd_dct} return yaml_gra_dct
def relabel(gra, atm_key_dct): """ relabel the graph with new atom keys """ orig_atm_keys = atom_keys(gra) assert set(atm_key_dct.keys()) <= orig_atm_keys, ('{}\n{}'.format( set(atm_key_dct.keys()), orig_atm_keys)) new_atm_key_dct = dict(zip(orig_atm_keys, orig_atm_keys)) new_atm_key_dct.update(atm_key_dct) _relabel_atom_key = new_atm_key_dct.__getitem__ def _relabel_bond_key(bnd_key): return frozenset(map(_relabel_atom_key, bnd_key)) atm_dct = dict_.transform_keys(atoms(gra), _relabel_atom_key) bnd_dct = dict_.transform_keys(bonds(gra), _relabel_bond_key) return from_atoms_and_bonds(atm_dct, bnd_dct)
def from_yaml_dictionary(yaml_gra_dct, one_indexed=True): """ read the graph from a yaml dictionary """ atm_dct = yaml_gra_dct['atoms'] bnd_dct = yaml_gra_dct['bonds'] atm_dct = dict_.transform_values( atm_dct, lambda x: tuple(map(x.__getitem__, ATM_PROP_NAMES))) bnd_dct = dict_.transform_keys(bnd_dct, lambda x: frozenset(map(int, x.split('-')))) bnd_dct = dict_.transform_values( bnd_dct, lambda x: tuple(map(x.__getitem__, BND_PROP_NAMES))) gra = _create.from_atoms_and_bonds(atm_dct, bnd_dct) if one_indexed: # revert one-indexing if the input is one-indexed atm_key_dct = {atm_key: atm_key - 1 for atm_key in atom_keys(gra)} gra = relabel(gra, atm_key_dct) return gra
def distance_ranges_from_coordinates(gra, dist_dct, ang_dct=None, dih_dct=None, angstrom=True, degree=True, rings_keys=(), keys=None, check=False): """ generate a set of distance ranges from coordinate values :param gra: molecular graph atom keys specifying the order of indices in the matrix :param dist_dct: a dictionary of desired distances for certain atoms; the keys are pairs of atoms, the values are distances in angstroms :type dist_dct: dict[(int, int): float] :param ang_dct: a dictionary of desired angles for certain atoms; the keys are triples of atoms; if the first or last element in a triple is None, an appopriate neighboring atom will be found :param dih_dct: a dictionary of desired angles for certain atoms; the keys are quadruples of atoms; if the first or last element in a triple is None, an appopriate neighboring atom will be found :type dist_dct: dict[(int, int, int): float] :param rings_keys: keys for rings in the graph; angle ranges will automatically be set to allow ring formation :param keys: set of keys that can be used to fill in the angle keys; if None, all graph keys will be considered available for use :param check: check the angle keys to make sure they can all be filled in? """ keys = atom_keys(gra) if keys is None else keys ang_dct = {} if ang_dct is None else ang_dct dih_dct = {} if dih_dct is None else dih_dct # Fill in angle keys ang_key_filler_ = angle_key_filler_(gra, keys, check=check) ang_dct = dict_.transform_keys(ang_dct, ang_key_filler_) if None in ang_dct: ang_dct.pop(None) # Fill in dihedral keys dih_dct = dict_.transform_keys(dih_dct, ang_key_filler_) if None in dih_dct: dih_dct.pop(None) # Convert angles into distances dist_dct = dict_.transform_keys(dist_dct, frozenset) for (key1, key2, key3), a123 in ang_dct.items(): a123 *= phycon.DEG2RAD if degree else 1. k12 = frozenset({key1, key2}) k23 = frozenset({key2, key3}) k13 = frozenset({key1, key3}) d12 = (dist_dct[k12] if k12 in dist_dct else heuristic_bond_distance(gra, key1, key2, angstrom=angstrom)) d23 = (dist_dct[k23] if k23 in dist_dct else heuristic_bond_distance(gra, key2, key3, angstrom=angstrom)) d13 = numpy.sqrt(d12**2 + d23**2 - 2*d12*d23*numpy.cos(a123)) dist_dct[k13] = d13 # Convert convert fixed distances into ranges dist_range_dct = {k: (d, d) for k, d in dist_dct.items()} # Convert dihedrals into distances for (key1, key2, key3, key4), val in dih_dct.items(): # Allow user to leave dihedrals open-ended, as a lower or upper bound if isinstance(val, numbers.Number): d1234 = val else: assert hasattr(val, '__len__') and len(val) == 2 d1234 = next(v for v in val if v is not None) d1234 *= phycon.DEG2RAD if degree else 1. k12 = frozenset({key1, key2}) k23 = frozenset({key2, key3}) k34 = frozenset({key3, key4}) k13 = frozenset({key1, key3}) k24 = frozenset({key2, key4}) k14 = frozenset({key1, key4}) d12 = (dist_dct[k12] if k12 in dist_dct else heuristic_bond_distance(gra, key1, key2, angstrom=angstrom)) d23 = (dist_dct[k23] if k23 in dist_dct else heuristic_bond_distance(gra, key2, key3, angstrom=angstrom)) d34 = (dist_dct[k34] if k34 in dist_dct else heuristic_bond_distance(gra, key3, key4, angstrom=angstrom)) d13 = (dist_dct[k13] if k13 in dist_dct else heuristic_bond_distance(gra, key1, key3, angstrom=angstrom)) d24 = (dist_dct[k24] if k24 in dist_dct else heuristic_bond_distance(gra, key2, key4, angstrom=angstrom)) term1 = (d12**2 + d23**2 - d13**2)*(d23**2 + d34**2 - d24**2) term2 = 2*d23**2 * (d13**2 + d24**2 - d23**2) denom = numpy.sqrt( (4*d12**2 * d23**2 - (d12**2 + d23**2 - d13**2)**2) * (4*d23**2 * d34**2 - (d23**2 + d34**2 - d24**2)**2)) d14 = numpy.sqrt((term1 + term2 - numpy.cos(d1234) * denom) / (2 * d23**2)) if isinstance(val, numbers.Number) or val[0] == val[1]: dist_range_dct[k14] = (d14, d14) elif val[0] is None: ld14 = closest_approach(gra, key1, key4) dist_range_dct[k14] = (ld14, d14) elif val[1] is None: ud14 = 999. dist_range_dct[k14] = (d14, ud14) else: raise ValueError("Invalid dih_dict: {}".format(str(dih_dct))) for rng_keys in rings_keys: assert hasattr(keys, '__iter__'), ( "Please pass in rings keys as a list of lists") rsz = len(rng_keys) a123 = (rsz - 2.) * 180. / rsz la123 = (a123 - 10.) * phycon.DEG2RAD ua123 = (a123 + 10.) * phycon.DEG2RAD for key1, key2, key3 in mit.windowed(rng_keys + rng_keys[:2], 3): k12 = frozenset({key1, key2}) k23 = frozenset({key2, key3}) k13 = frozenset({key1, key3}) d12 = (dist_dct[k12] if k12 in dist_dct else heuristic_bond_distance(gra, key1, key2, angstrom=angstrom)) d23 = (dist_dct[k23] if k23 in dist_dct else heuristic_bond_distance(gra, key2, key3, angstrom=angstrom)) ld13 = numpy.sqrt(d12**2 + d23**2 - 2*d12*d23*numpy.cos(la123)) ud13 = numpy.sqrt(d12**2 + d23**2 - 2*d12*d23*numpy.cos(ua123)) dist_range_dct[k13] = (ld13, ud13) return dist_range_dct