def list_potential_bonds(atmlist0): """ Given a list of atoms, return a list of triples (cost, atm1, atm2) for all bondable pairs of atoms in the list. Each pair of atoms is considered separately, as if only it would be bonded, in addition to all existing bonds. In other words, the returned bonds can't necessarily all be made (due to atom valence), but any one alone can be made, in addition to whatever bonds the atoms currently have. Warning: the current implementation takes quadratic time in len(atmlist0). The return value will have reasonable size for physically realistic atmlists, but could be quadratic in size for unrealistic ones (e.g. if all atom positions were compressed into a small region of space). """ atmlist = filter( bondable_atm, atmlist0 ) lst = [] maxBondLength = 2.0 ngen = NeighborhoodGenerator(atmlist, maxBondLength) for atm1 in atmlist: key1 = atm1.key pos1 = atm1.posn() for atm2 in ngen.region(pos1): bondLen = vlen(pos1 - atm2.posn()) idealBondLen = idealBondLength(atm1, atm2) if atm2.key < key1 and bondLen < max_dist_ratio(atm1, atm2) * idealBondLen: # i.e. for each pair (atm1, atm2) of bondable atoms cost = bond_cost(atm1, atm2) if cost is not None: lst.append((cost, atm1, atm2)) lst.sort() # least cost first return lst
def inferBonds(mol): # [probably by Will; TODO: needs docstring] # bruce 071030 moved this from bonds.py to bonds_from_atoms.py # not sure how big a margin we should have for "coincident" maxBondLength = 2.0 # first remove any coincident singlets singlets = filter(lambda a: a.is_singlet(), mol.atoms.values()) removable = {} sngen = NeighborhoodGenerator(singlets, maxBondLength) for sing1 in singlets: key1 = sing1.key pos1 = sing1.posn() for sing2 in sngen.region(pos1): key2 = sing2.key dist = vlen(pos1 - sing2.posn()) if key1 != key2: removable[key1] = sing1 removable[key2] = sing2 for badGuy in removable.values(): badGuy.kill() from operations.bonds_from_atoms import make_bonds make_bonds(mol.atoms.values()) return
def __init__(self): self.repeated_bonds_dict = {} # Note: this env reference may cause undesirable usage tracking, # depending on when it occurs. This should cause no harm -- # only a needless display list remake when the pref is changed. self.indicate_overlapping_atoms = \ env.prefs[indicateOverlappingAtoms_prefs_key] if self.indicate_overlapping_atoms: TOO_CLOSE = 0.3 # stub, guess; needs to not be true even for # bonded atoms, or atoms and their bondpoints, # but big enough to catch "visually hard to separate" atoms. # (1.0 is far too large; 0.1 is ok but too small to be best.) # It would be much better to let this depend on the elements # and whether they're bonded, and not hard to implement # (each atom could be asked whether it's too close to each # other one, and take all this into account). If we do that, # this value should be the largest tolerance for any pair # of atoms, and the code that uses this NeighborhoodGenerator # should do more filtering on the results. [bruce 080411] self._f_state_for_indicate_overlapping_atoms = \ NeighborhoodGenerator( [], TOO_CLOSE, include_singlets = True ) pass return
def add_to_mol(self, mol): maxradius = 1.5 * self.bondlength positions = self.carbons() atoms = [ ] for newpos in positions: newguy = Atom('C', newpos, mol) atoms.append(newguy) newguy.set_atomtype('sp2') ngen = NeighborhoodGenerator(atoms, maxradius) for atm1 in atoms: p1 = atm1.posn() for atm2 in ngen.region(p1): if atm2.key < atm1.key: bond_atoms(atm1, atm2, V_GRAPHITE) # clean up singlets for atm in atoms: for s in atm.singNeighbors(): s.kill() atm.make_enough_bondpoints()
def add_to_mol(self, mol): maxradius = 1.5 * self.bondlength positions = self.carbons() atoms = [] for newpos in positions: newguy = Atom('C', newpos, mol) atoms.append(newguy) newguy.set_atomtype('sp2') ngen = NeighborhoodGenerator(atoms, maxradius) for atm1 in atoms: p1 = atm1.posn() for atm2 in ngen.region(p1): if atm2.key < atm1.key: bond_atoms(atm1, atm2, V_GRAPHITE) # clean up singlets for atm in atoms: for s in atm.singNeighbors(): s.kill() atm.make_enough_bondpoints()
def inferBonds(mol): # [probably by Will; TODO: needs docstring] #bruce 071030 moved this from bonds.py to bonds_from_atoms.py # not sure how big a margin we should have for "coincident" maxBondLength = 2.0 # first remove any coincident singlets singlets = filter(lambda a: a.is_singlet(), mol.atoms.values()) removable = { } sngen = NeighborhoodGenerator(singlets, maxBondLength) for sing1 in singlets: key1 = sing1.key pos1 = sing1.posn() for sing2 in sngen.region(pos1): key2 = sing2.key dist = vlen(pos1 - sing2.posn()) if key1 != key2: removable[key1] = sing1 removable[key2] = sing2 for badGuy in removable.values(): badGuy.kill() from operations.bonds_from_atoms import make_bonds make_bonds(mol.atoms.values()) return
def addCarbons(chopSpace, R=radius): # a buckyball has no more than about 6*r**2 atoms, r in angstroms # each cap is ideally a half-buckyball for i in range(int(3.0 * R**2)): regional_singlets = filter(lambda atm: chopSpace(atm) and atm.is_singlet(), mol.atoms.values()) for s in regional_singlets: s.setposn(projectOntoSphere(s.posn())) if len(regional_singlets) < 3: # there won't be anything to bond to anyway, let the user # manually adjust the geometry return singlet_pair = None try: for s1 in regional_singlets: s1p = s1.posn() for s2 in regional_singlets: if s2.key > s1.key and \ vlen(s2.posn() - s1p) < bondlength: singlet_pair = (s1, s2) # break out of both for-loops raise Exception except: pass if singlet_pair is not None: # if there is an existing pair of singlets that's close than one bond # length, use those to make the newguy, so he'll have one open bond left sing1, sing2 = singlet_pair owner1, owner2 = sing1.realNeighbors()[0], sing2.realNeighbors()[0] newpos1 = walk_great_circle(owner1.posn(), sing1.posn(), bondlength) newpos2 = walk_great_circle(owner2.posn(), sing2.posn(), bondlength) newpos = 0.5 * (newpos1 + newpos2) regional_singlets.remove(sing1) regional_singlets.remove(sing2) else: # otherwise choose any pre-existing bond and stick the newguy on him # prefer a bond whose real atom already has two real neighbors preferred = filter(lambda atm: len(atm.realNeighbors()[0].realNeighbors()) == 2, regional_singlets) if preferred: sing = preferred[0] else: sing = regional_singlets[0] owner = sing.realNeighbors()[0] newpos = walk_great_circle(owner.posn(), sing.posn(), bondlength) regional_singlets.remove(sing) ngen = NeighborhoodGenerator(mol.atoms.values(), 1.1 * bondlength) # do not include new guy in neighborhood, add him afterwards newguy = Atom('C', newpos, mol) newguy.set_atomtype('sp2') # if the new atom is close to an older atom, merge them: kill the newer # atom, give the older one its neighbors, nudge the older one to the midpoint for oldguy in ngen.region(newpos): if vlen(oldguy.posn() - newpos) < 0.4: newpos = 0.5 * (newguy.posn() + oldguy.posn()) newguy.setposn(newpos) ngen.remove(oldguy) oldguy.kill() break # Bond with anybody close enough. The newer make_bonds # code doesn't seem to handle this usage very well. for oldguy in ngen.region(newpos): r = oldguy.posn() - newpos rlen = vlen(r) if (len(newguy.realNeighbors()) < 3 and rlen < 1.1 * bondlength): if rlen < 0.7 * bondlength: # nudge them apart nudge = ((0.7 * bondlength - rlen) / rlen) * r oldguy.setposn(oldguy.posn() + 0.5 * r) newguy.setposn(newguy.posn() - 0.5 * r) bond_atoms(newguy, oldguy, V_GRAPHITE) cleanupSinglets(newguy) cleanupSinglets(oldguy) if len(newguy.realNeighbors()) > 3: print 'warning: too many bonds on newguy' # Try moving the new guy around to make his bonds closer to bondlength but # keep him on or near the surface of the sphere. Use Newton's method in # three dimensions. def error(posn): e = (vlen(posn - sphere_center) - radius) ** 2 for atm in newguy.realNeighbors(): e += (vlen(atm.posn() - posn) - bondlength)**2 return e p = newguy.posn() for i in range(2): h = 1.0e-4 e0 = error(p) gradient = V((error(p + V(h, 0, 0)) - e0) / h, (error(p + V(0, h, 0)) - e0) / h, (error(p + V(0, 0, h)) - e0) / h) p = p - (e0 / vlen(gradient)**2) * gradient newguy.setposn(p) # we may need to reposition singlets for atm in ngen.region(newguy.posn()): cleanupSinglets(atm) cleanupSinglets(newguy)
def addCarbons(chopSpace, R=radius): # a buckyball has no more than about 6*r**2 atoms, r in angstroms # each cap is ideally a half-buckyball for i in range(int(3.0 * R**2)): regional_singlets = filter( lambda atm: chopSpace(atm) and atm.is_singlet(), mol.atoms.values()) for s in regional_singlets: s.setposn(projectOntoSphere(s.posn())) if len(regional_singlets) < 3: # there won't be anything to bond to anyway, let the user # manually adjust the geometry return singlet_pair = None try: for s1 in regional_singlets: s1p = s1.posn() for s2 in regional_singlets: if s2.key > s1.key and \ vlen(s2.posn() - s1p) < bondlength: singlet_pair = (s1, s2) # break out of both for-loops raise Exception except: pass if singlet_pair is not None: # if there is an existing pair of singlets that's close than one bond # length, use those to make the newguy, so he'll have one open bond left sing1, sing2 = singlet_pair owner1, owner2 = sing1.realNeighbors()[0], sing2.realNeighbors( )[0] newpos1 = walk_great_circle(owner1.posn(), sing1.posn(), bondlength) newpos2 = walk_great_circle(owner2.posn(), sing2.posn(), bondlength) newpos = 0.5 * (newpos1 + newpos2) regional_singlets.remove(sing1) regional_singlets.remove(sing2) else: # otherwise choose any pre-existing bond and stick the newguy on him # prefer a bond whose real atom already has two real neighbors preferred = filter( lambda atm: len(atm.realNeighbors()[0].realNeighbors()) == 2, regional_singlets) if preferred: sing = preferred[0] else: sing = regional_singlets[0] owner = sing.realNeighbors()[0] newpos = walk_great_circle(owner.posn(), sing.posn(), bondlength) regional_singlets.remove(sing) ngen = NeighborhoodGenerator(mol.atoms.values(), 1.1 * bondlength) # do not include new guy in neighborhood, add him afterwards newguy = Atom('C', newpos, mol) newguy.set_atomtype('sp2') # if the new atom is close to an older atom, merge them: kill the newer # atom, give the older one its neighbors, nudge the older one to the midpoint for oldguy in ngen.region(newpos): if vlen(oldguy.posn() - newpos) < 0.4: newpos = 0.5 * (newguy.posn() + oldguy.posn()) newguy.setposn(newpos) ngen.remove(oldguy) oldguy.kill() break # Bond with anybody close enough. The newer make_bonds # code doesn't seem to handle this usage very well. for oldguy in ngen.region(newpos): r = oldguy.posn() - newpos rlen = vlen(r) if (len(newguy.realNeighbors()) < 3 and rlen < 1.1 * bondlength): if rlen < 0.7 * bondlength: # nudge them apart nudge = ((0.7 * bondlength - rlen) / rlen) * r oldguy.setposn(oldguy.posn() + 0.5 * r) newguy.setposn(newguy.posn() - 0.5 * r) bond_atoms(newguy, oldguy, V_GRAPHITE) cleanupSinglets(newguy) cleanupSinglets(oldguy) if len(newguy.realNeighbors()) > 3: print 'warning: too many bonds on newguy' # Try moving the new guy around to make his bonds closer to bondlength but # keep him on or near the surface of the sphere. Use Newton's method in # three dimensions. def error(posn): e = (vlen(posn - sphere_center) - radius)**2 for atm in newguy.realNeighbors(): e += (vlen(atm.posn() - posn) - bondlength)**2 return e p = newguy.posn() for i in range(2): h = 1.0e-4 e0 = error(p) gradient = V((error(p + V(h, 0, 0)) - e0) / h, (error(p + V(0, h, 0)) - e0) / h, (error(p + V(0, 0, h)) - e0) / h) p = p - (e0 / vlen(gradient)**2) * gradient newguy.setposn(p) # we may need to reposition singlets for atm in ngen.region(newguy.posn()): cleanupSinglets(atm) cleanupSinglets(newguy)
def joinNeighboringStrands(self, strand, endChoice = 'THREE_PRIME_END'): """ Join the 3 or 5' end of the given strand with a 5' or 3' (respt) end of a neighboring strand, on the SAME DnaSegment. @param strand: The DnaStrand whose 3' or 5' end will be joined with the 5' or 3' end base atom (respectively) on a neighboring strand of the same DnaSegment. @type strand: DnaStrand @see: self._bond_two_strandAtoms() @TODO: implement code when endChoice is 'Five prime end'. At the moment, it works only for the the 3' end atom of <strand>. @see: ClickToJoinStrands_GraphicsMode.chunkLeftUp() """ if not isinstance(strand, self.assy.DnaStrand): print_compact_stack("bug: %s is not a DnaStrand instance"%strand) return bool_recursively_join_strands = env.prefs[joinStrandsCommand_recursive_clickToJoinDnaStrands_prefs_key] if endChoice == 'THREE_PRIME_END': current_strand = strand count = 0 #This implements a NFR by Mark. Recursively join the DnaStrand's #3 prime end with the 5' end of a neighboring strand. #This is SLOW for recursively joining strands. while(True): #If the 'recursivly join DnaStrand option is not checked #return immediately after the first interation (which joins #the two neighboring strands) if count == 1 and not bool_recursively_join_strands: return endAtom = current_strand.get_three_prime_end_base_atom() if endAtom is None: return #Now find the nearest five prime end atom on a strand of the #*same* Dna segment. axis_atom = endAtom.axis_neighbor() if axis_atom is None: return segment = current_strand.get_DnaSegment_with_content_atom(endAtom) if segment is None: return #find all five prime end base atoms contained by this DnaSegment. raw_five_prime_ends = segment.get_all_content_five_prime_ends() def func(atm): """ Returns true if the given atom's strand is not same as the 'strand' which is 'endAoms' parent DnaStrand """ if atm.getDnaStrand() is current_strand: return False return True #Following ensures that the three prime end of <strand> won't be #bonded to the five prime end of the same strand. five_prime_ends = filter(lambda atm: func(atm), raw_five_prime_ends) #Max radius within which to search for the 'neighborhood' atoms #(five prime ends) maxBondLength = 10.0 neighborHood = NeighborhoodGenerator(five_prime_ends, maxBondLength) pos = endAtom.posn() region_atoms = neighborHood.region(pos) #minor optimization if not region_atoms: if not bool_recursively_join_strands: print_compact_stack( "No five prime end atoms found" \ "within %f A radius of the "\ "strand's 3 prime end"%(maxBondLength)) return elif len(region_atoms) == 1: five_prime_end_atom = region_atoms[0] else: lst = [] for atm in region_atoms: length = vlen(pos - atm.posn()) lst.append((length, atm)) lst.sort() #Five prime end atom nearest to the 'endAtom' is the contained #within the first tuple of the sorted list 'tpl' length, five_prime_end_atom = lst[0] self._bond_two_strandAtoms(endAtom, five_prime_end_atom) self.assy.update_parts() current_strand = endAtom.getDnaStrand() count += 1 elif endChoice == 'FIVE_PRIME_END': #NOT IMPLEMENTED AS OF 2008-10-26 endAtom = strand.get_five_prime_end_base_atom() if endAtom is None: return
def joinNeighboringStrands(self, strand, endChoice='THREE_PRIME_END'): """ Join the 3 or 5' end of the given strand with a 5' or 3' (respt) end of a neighboring strand, on the SAME DnaSegment. @param strand: The DnaStrand whose 3' or 5' end will be joined with the 5' or 3' end base atom (respectively) on a neighboring strand of the same DnaSegment. @type strand: DnaStrand @see: self._bond_two_strandAtoms() @TODO: implement code when endChoice is 'Five prime end'. At the moment, it works only for the the 3' end atom of <strand>. @see: ClickToJoinStrands_GraphicsMode.chunkLeftUp() """ if not isinstance(strand, self.assy.DnaStrand): print_compact_stack("bug: %s is not a DnaStrand instance" % strand) return bool_recursively_join_strands = env.prefs[ joinStrandsCommand_recursive_clickToJoinDnaStrands_prefs_key] if endChoice == 'THREE_PRIME_END': current_strand = strand count = 0 #This implements a NFR by Mark. Recursively join the DnaStrand's #3 prime end with the 5' end of a neighboring strand. #This is SLOW for recursively joining strands. while (True): #If the 'recursivly join DnaStrand option is not checked #return immediately after the first interation (which joins #the two neighboring strands) if count == 1 and not bool_recursively_join_strands: return endAtom = current_strand.get_three_prime_end_base_atom() if endAtom is None: return #Now find the nearest five prime end atom on a strand of the #*same* Dna segment. axis_atom = endAtom.axis_neighbor() if axis_atom is None: return segment = current_strand.get_DnaSegment_with_content_atom( endAtom) if segment is None: return #find all five prime end base atoms contained by this DnaSegment. raw_five_prime_ends = segment.get_all_content_five_prime_ends() def func(atm): """ Returns true if the given atom's strand is not same as the 'strand' which is 'endAoms' parent DnaStrand """ if atm.getDnaStrand() is current_strand: return False return True #Following ensures that the three prime end of <strand> won't be #bonded to the five prime end of the same strand. five_prime_ends = filter(lambda atm: func(atm), raw_five_prime_ends) #Max radius within which to search for the 'neighborhood' atoms #(five prime ends) maxBondLength = 10.0 neighborHood = NeighborhoodGenerator(five_prime_ends, maxBondLength) pos = endAtom.posn() region_atoms = neighborHood.region(pos) #minor optimization if not region_atoms: if not bool_recursively_join_strands: print_compact_stack( "No five prime end atoms found" \ "within %f A radius of the "\ "strand's 3 prime end"%(maxBondLength)) return elif len(region_atoms) == 1: five_prime_end_atom = region_atoms[0] else: lst = [] for atm in region_atoms: length = vlen(pos - atm.posn()) lst.append((length, atm)) lst.sort() #Five prime end atom nearest to the 'endAtom' is the contained #within the first tuple of the sorted list 'tpl' length, five_prime_end_atom = lst[0] self._bond_two_strandAtoms(endAtom, five_prime_end_atom) self.assy.update_parts() current_strand = endAtom.getDnaStrand() count += 1 elif endChoice == 'FIVE_PRIME_END': #NOT IMPLEMENTED AS OF 2008-10-26 endAtom = strand.get_five_prime_end_base_atom() if endAtom is None: return