def debug_check_bond_direction(self, when = ""): """ Verify our bond direction is set correctly (if possible), and assertfail and/or print a debug warning if not. """ ## assert self.strandQ assert self.baseatoms, "%r has no baseatoms" % self if not self.strandQ: return assert self._bond_direction assert not self._bond_direction_error if self.bond_direction_is_arbitrary(): return # no way to check it # verify it by comparing it to actual bonds if when: when = " (%s)" % when # STUB: only works fully for PAM3 atom1 = self.baseatoms[0] atom2 = self.baseatoms[1] bond = find_bond(atom1, atom2) errormsg = "" # might be set to an error string actual_direction = 0 # might be set to a bond direction if bond: actual_direction = bond.bond_direction_from(atom1) elif atom1.Pl_neighbors() or atom2.Pl_neighbors(): # look for atom1-Pl-atom2 (2 bonds, same direction) bond1, bond2 = find_Pl_bonds(atom1, atom2) # might be None, None if not bond1: errormsg = "no Pl5 between adjacent baseatoms %r and %r" % (atom1, atom2) else: dir1 = bond1.bond_direction_from(atom1) dir2 = - bond2.bond_direction_from(atom2) if dir1 == dir2: actual_direction = dir1 # might be 0 else: errormsg = "Pl5 between %r and %r has inconsistent or unset bond directions" % (atom1, atom2) else: errormsg = "no bond between adjacent baseatoms %r and %r" % (atom1, atom2) if not errormsg: # check actual_direction ## not needed: assert actual_direction recorded_direction = self._bond_direction assert recorded_direction # redundant with earlier assert if actual_direction != recorded_direction: # error errormsg = "bond direction from %r to %r: recorded %r, actual %r" % \ (atom1, atom2, recorded_direction, actual_direction) # now errormsg tells whether there is an error. if errormsg: prefix = "debug_check_bond_direction%s in %r" % (when, self) msg = "%s: ERROR: %s" % (prefix, errormsg) print "\n*** %s ***\n" % msg summary_format = "DNA updater: bug: [N] failure(s) of debug_check_bond_direction, see console prints" env.history.deferred_summary_message( redmsg(summary_format)) return
def _insert_Pl_between_0(s1, s2): direct_bond = find_bond(s1, s2) assert direct_bond direction = direct_bond.bond_direction_from(s1) # from s1 to s2 # Figure out which atom the Pl sticks to (joins the chunk of). # Note: the following works even if s1 or s2 is a bondpoint, # which is needed for putting a Pl at the end of a newly-PAM5 strand. # But whether to actually put one there is up to the caller -- # it's only correct at one end, but this function will always do it. if direction == Pl_STICKY_BOND_DIRECTION: Pl_prefers = [s2, s1] else: Pl_prefers = [s1, s2] # The Pl sticks to the first thing in Pl_prefers which is not a bondpoint # (which always exists, since two bondpoints can't be bonded): # (see also the related method Pl_preferred_Ss_neighbor()) Pl_sticks_to = None # for pylint for s in Pl_prefers: if not s.is_singlet(): Pl_sticks_to = s break continue # break the old bond... wait, do this later, # so as not to kill s1 or s2 if one of them is a Singlet ## direct_bond.bust(make_bondpoints = False) # make the new Pl atom ## Atom = s1.__class__ ## # kluge to avoid import of chem.py, for now ## # (though that would probably be ok (at least as a runtime import) ## # even though it might expand the import cycle graph) ## # needs revision if we introduce Atom subclasses ## # TODO: check if this works when Atom is an extension object ## # (from pyrex atoms) Atom = s1.molecule.assy.Atom #bruce 080516 chunk = Pl_sticks_to.molecule pos = (s1.posn() + s2.posn()) / 2.0 # position will be corrected by caller before user sees it Pl = Atom('Pl5', pos, chunk) Pl._f_Pl_posn_is_definitive = False # tell caller it needs to, and is allowed to, correct pos # bond it to s1 and s2 b1 = bond_atoms_faster(Pl, s1, V_SINGLE) b2 = bond_atoms_faster(Pl, s2, V_SINGLE) # now it should be safe to break the old bond direct_bond.bust(make_bondpoints=False) # set bond directions: s1->Pl->s2 same as s1->s2 was before b1.set_bond_direction_from(s1, direction) b2.set_bond_direction_from(Pl, direction) return Pl # or None if error? caller assumes not possible, so do we
def makeCrossover(self, crossoverPairs, suppress_post_crossover_updates=False): """ Make the crossover between the atoms of the crossover pairs. @param crossoverPairs: A tuple of 4 atoms between which the crossover will be made. Note: As of 2008-06-03, this method assumes the following form: (atom1, neighbor1, atom2, neighbor2) Where all atoms are PAM3 atoms. pair of atoms atom1 and neighbor1 are sugar atoms bonded to each other (same for pair atom2, neighbor2) The bond between these atoms will be broken first and then the atoms are bonded to the opposite atoms. @type crossoverPair: tuple @param suppress_post_crossover_updates: After making a crossover, this method calls its graphicsMode method to do some more updates (such as updating the atoms dictionaries etc.) But if its a batch process, (e.g. user is calling makeAllCrossovers, this update is not needed after making an individual crossover. The caller then sets this flag to true to tell this method to skip that update. @type suppress_post_crossover_updates: boolean @seE:self.makeAllCrossovers() """ if len(crossoverPairs) != 4: print_compact_stack( "Bug in making the crossover.len(crossoverPairs) != 4") return atm1, neighbor1, atm2, neighbor2 = crossoverPairs bond1 = find_bond(atm1, neighbor1) if bond1: bond1.bust() bond2 = find_bond(atm2, neighbor2) if bond2: bond2.bust() #Do we need to check if these pairs are valid (i.e.a 5' end atom is #bonded to a 3' end atom.. I think its not needed as its done in #self._bond_two_strandAtoms. self._bond_two_strandAtoms(atm1, neighbor2) self._bond_two_strandAtoms(atm2, neighbor1) if not suppress_post_crossover_updates: self.graphicsMode.update_after_crossover_creation(crossoverPairs)
def makeCrossover(self, crossoverPairs, suppress_post_crossover_updates = False): """ Make the crossover between the atoms of the crossover pairs. @param crossoverPairs: A tuple of 4 atoms between which the crossover will be made. Note: As of 2008-06-03, this method assumes the following form: (atom1, neighbor1, atom2, neighbor2) Where all atoms are PAM3 atoms. pair of atoms atom1 and neighbor1 are sugar atoms bonded to each other (same for pair atom2, neighbor2) The bond between these atoms will be broken first and then the atoms are bonded to the opposite atoms. @type crossoverPair: tuple @param suppress_post_crossover_updates: After making a crossover, this method calls its graphicsMode method to do some more updates (such as updating the atoms dictionaries etc.) But if its a batch process, (e.g. user is calling makeAllCrossovers, this update is not needed after making an individual crossover. The caller then sets this flag to true to tell this method to skip that update. @type suppress_post_crossover_updates: boolean @seE:self.makeAllCrossovers() """ if len(crossoverPairs) != 4: print_compact_stack("Bug in making the crossover.len(crossoverPairs) != 4") return atm1, neighbor1, atm2, neighbor2 = crossoverPairs bond1 = find_bond(atm1, neighbor1) if bond1: bond1.bust() bond2 = find_bond(atm2, neighbor2) if bond2: bond2.bust() #Do we need to check if these pairs are valid (i.e.a 5' end atom is #bonded to a 3' end atom.. I think its not needed as its done in #self._bond_two_strandAtoms. self._bond_two_strandAtoms(atm1, neighbor2) self._bond_two_strandAtoms(atm2, neighbor1) if not suppress_post_crossover_updates: self.graphicsMode.update_after_crossover_creation(crossoverPairs)
def _insert_Pl_between_0(s1, s2): direct_bond = find_bond(s1, s2) assert direct_bond direction = direct_bond.bond_direction_from(s1) # from s1 to s2 # Figure out which atom the Pl sticks to (joins the chunk of). # Note: the following works even if s1 or s2 is a bondpoint, # which is needed for putting a Pl at the end of a newly-PAM5 strand. # But whether to actually put one there is up to the caller -- # it's only correct at one end, but this function will always do it. if direction == Pl_STICKY_BOND_DIRECTION: Pl_prefers = [s2, s1] else: Pl_prefers = [s1, s2] # The Pl sticks to the first thing in Pl_prefers which is not a bondpoint # (which always exists, since two bondpoints can't be bonded): # (see also the related method Pl_preferred_Ss_neighbor()) Pl_sticks_to = None # for pylint for s in Pl_prefers: if not s.is_singlet(): Pl_sticks_to = s break continue # break the old bond... wait, do this later, # so as not to kill s1 or s2 if one of them is a Singlet ## direct_bond.bust(make_bondpoints = False) # make the new Pl atom ## Atom = s1.__class__ ## # kluge to avoid import of chem.py, for now ## # (though that would probably be ok (at least as a runtime import) ## # even though it might expand the import cycle graph) ## # needs revision if we introduce Atom subclasses ## # TODO: check if this works when Atom is an extension object ## # (from pyrex atoms) Atom = s1.molecule.assy.Atom #bruce 080516 chunk = Pl_sticks_to.molecule pos = (s1.posn() + s2.posn()) / 2.0 # position will be corrected by caller before user sees it Pl = Atom('Pl5', pos, chunk) Pl._f_Pl_posn_is_definitive = False # tell caller it needs to, and is allowed to, correct pos # bond it to s1 and s2 b1 = bond_atoms_faster(Pl, s1, V_SINGLE) b2 = bond_atoms_faster(Pl, s2, V_SINGLE) # now it should be safe to break the old bond direct_bond.bust(make_bondpoints = False) # set bond directions: s1->Pl->s2 same as s1->s2 was before b1.set_bond_direction_from(s1, direction) b2.set_bond_direction_from(Pl, direction) return Pl # or None if error? caller assumes not possible, so do we
def _found_ring(self, listb, lista): """ @see: make_ring [subclasses should extend make_ring, which we call, rather than this method] """ assert len(listb) == len(lista), "%r finds ring but #bonds %r != #atoms %r" % (self, len(listb), len(lista)) if 0 and "debug, but REMOVE WHEN WORKS, very slow": for i in range(len(listb)): assert find_bond(lista[i], lista[(i - 1) % len(lista)]) is listb[i] print "remove when works! in _found_ring len %d" % len(lista) return self.make_ring(listb, lista)
def _found_ring(self, listb, lista): """ @see: make_ring [subclasses should extend make_ring, which we call, rather than this method] """ assert len(listb) == len(lista), \ "%r finds ring but #bonds %r != #atoms %r" % \ (self, len(listb), len(lista)) if 0 and 'debug, but REMOVE WHEN WORKS, very slow': for i in range(len(listb)): assert find_bond(lista[i], lista[(i - 1) % len(lista)]) is listb[i] print "remove when works! in _found_ring len %d" % len(lista) return self.make_ring(listb, lista)
def find_Pl_between(s1, s2): #bruce 080409 """ Assume s1 and s2 are Ss3 and/or Ss5 atoms which might be directly bonded or might have a Pl5 between them. If they are directly bonded, return None. If they have a Pl between them, return it. If neither is true, raise an exception. """ # optimize for the Pl being found bond1, bond2 = find_Pl_bonds(s1, s2) if bond1: return bond1.other(s1) else: assert find_bond(s1, s2) return None pass
def make_or_remove_crossover(twoPls, make=True, cmdname=None): """ Make or Remove (according to make option) a crossover, given Pl5_recognizers for its two Pl atoms. """ # What we are doing is recognizing one local structure and replacing it with another # made from the same atoms. It'd sure be easier if I could do the manipulation in an mmp file, # save that somewhere, and read those to generate the operation! I'd have two sets of atoms, before and after, # and see how bonds and atomtypes got changed. # In this case it's not too hard to hand-code... I guess only the Pl atoms and their bonds are affected. # We probably do have to look at strand directions -- hmm, probably we should require them to exist before saying it's ok! # Or maybe better to give history error message when the command is chosen, saying you need to set them (or repair them) first... # Then we have to move the new/moved Pl atoms into a good position... # Note: Pl.ordered_bases are ordered by bond direction, to make this easier... # but if we want to patch up the directions in the end, do we need to care exactly which ones were defined? # or only "per-Pl"? hmm... it's per-Pl for now assert cmdname for pl in twoPls: if pl.ordered_bases is None: # should no longer be possible -- now checked before menu commands are offered [bruce 070604] ###BUG: this could have various causes, not only the one reported below! Somehow we need access to the # message supplied to the RecognizerError, for use here. ###REVIEW: Does that mean it should pass through compute methods (probably in a controlled way) # rather than making computed values None? # Or, should the value not be None, but a "frozen" examinable and reraisable version of the error exception?? msg = "%s: Error: bond direction is locally undefined or inconsistent around %s" % ( cmdname, pl.atom) ###UNTESTED print "should no longer be possible:", msg #bruce 070604 env.history.message(redmsg(quote_html(msg))) return Pl1, Pl2 = twoPls a, b = Pl1.ordered_bases d, c = Pl2.ordered_bases # note: we use d,c rather than c,d so that the atom arrangement is as shown in the diagram below. # Note: for either the Make or Remove operation, the geometric arrangement is initially: # # c <-- Pl2 <-- d # # a --> Pl1 --> b # # and it ends up being (where dots indicate arrowheads, to show bond direction): # # c d # . / # \ . # Pl1 Pl2 # . \ # / . # a b # # Note: Pl1 stays attached to a, and Pl2 to d. Which two opposite bonds to preserve like that # is an arbitrary choice -- as long as Make and Remove make the same choice about that, # they'll reverse each other's effects precisely (assuming the sugars were initially correct as Ss or Sj). # break the bonds we no longer want for obj1, obj2 in [(Pl1, b), (Pl2, c)]: bond = find_bond(obj1.atom, obj2.atom) bond.bust(make_bondpoints=False) # make the bonds we want and didn't already have for obj1, obj2 in [(Pl1, c), (Pl2, b)]: assert not atoms_are_bonded(obj1.atom, obj2.atom) ###e we should make bond_atoms do this assert itself, or maybe tolerate it (or does it already??) bond_atoms_faster(obj1.atom, obj2.atom, V_SINGLE) # set directions of all 4 bonds (even the preserved ones -- it's possible they were not set before, # if some but not all bonds had directions set in the part of a strand whose directions we look at.) for obj1, obj2 in [(a, Pl1), (Pl1, c), (d, Pl2), (Pl2, b)]: bond = find_bond(obj1.atom, obj2.atom) bond.set_bond_direction_from(obj1.atom, 1) # WARNING: after that bond rearrangement, don't use our Pl5_recognizers in ways that depend on Pl bonding, # since it's not well defined whether they think about the old or new bonding to give their answers. Pl_atoms = Pl1.atom, Pl2.atom del Pl1, Pl2, twoPls # transmute base sugars to Sj or Ss as appropriate if dna_updater_is_enabled(): want = Element_Ss5 #bruce 080320 bugfix else: want = make and Element_Sj5 or Element_Ss5 for obj in (a, b, c, d): obj.atom.Transmute(want) # Note: we do this after the bond making/breaking so it doesn't add singlets which mess us up. # move Pl atoms into better positions # (someday, consider using local minimize; for now, just place them directly between their new neighbor atoms, # hopefully we leave them selected so user can easily do their own local minimize.) for pl in Pl_atoms: pl.setposn( average_value(map(lambda neighbor: neighbor.posn(), pl.neighbors()))) env.history.message( greenmsg(cmdname + ": ") + quote_html("(%s - %s)" % tuple(Pl_atoms))) #e need assy.changed()? evidently not. return # from make_or_remove_crossover
def _reposition_baggage_1(self, baggage, other, planned_atom_nupos): """ """ # trivial cases len_baggage = len(baggage) if not len_baggage: return # cases handled well enough by calling code (as of 060629), # needing no correction here len_other = len(self.bonds) - len_baggage if not len_other: # should never happen, as we are called as of 060629, i think, # though if it did, there would be things we could do in theory, # like rotate atomtype.bondvecs to best match baggage... print "bug?: %r.reposition_baggage(%r) finds no other atoms -- " \ "nim, not thought to ever happen" % (self, baggage) return if len_other == 1: # the Q(old, new) code in the callers ought to handle it properly -- # UNLESS other is a pi_bond, and its alignment ought to affect a pair # of baggage atoms. if self.atomtype.spX == 2: # note: true for sp2 and sp2(graphitic) pass # let the main code run, including some other # "if len_other == 1" scattered around ##e someday: don't we want to also notice sp, and propogate # twisting of a pi_bond through an sp_chain? # I recall some code in depositMode for that... # I can't remember its scope, thus whether it covers this already... # I think not. ###@@@ else: return # at least 2 other (except sp2 pi_bond other), and at least one baggage... # might as well get other_posns we'll want to use # (handling planned_atom_nupos once and for all). if other == -1: other = [] baggage_keys = [atom.key for atom in baggage] for b in self.bonds: atom = b.other(self) if atom.key not in baggage_keys: other.append(atom) if len(other) != len_other: # must mean baggage is not a subset of neighbors args = (self, baggage, planned_atom_nupos) print "bug in reposition_baggage%r: len(other == %r) != len_other %r" % \ (args, other, len_other) return if len_other == 1: other0_bond = find_bond(other[0], self) if other0_bond.v6 == V_SINGLE: # calling code handled this case well enough return planned_atom, nupos = None, None if planned_atom_nupos: planned_atom, nupos = planned_atom_nupos if planned_atom not in other: print "likely bug in reposition_baggage: " \ "planned_atom not in other", planned_atom, other other_posns = [(atom.posn(), nupos)[atom is planned_atom] for atom in other] #e we might later wish we'd kept a list of the bonds to baggage and # other, to grab the v6's -- make a dict from atom.key above? selfposn = self.posn() othervecs = [norm(pos - selfposn) for pos in other_posns] bag_posns = [atom.posn() for atom in baggage] bagvecs = [norm(pos - selfposn) for pos in bag_posns] # The alg is specific to atomtype, and number and sometimes *type* of all # bonds involved. We'll code the most important and/or easiest cases first. # Someday we might move them into methods of the atomtypes themselves. algchoice = (self.atomtype.spX, len_baggage, len_other) # len_baggage >= 1, len_other >= 2 (except pi_bond case) extra = 0 # might be altered below if algchoice == (3, 2, 2) or algchoice == (3, 1, 2): # (3, 2, 2) -- e.g. C(sp3) with 2 bp's and 2 real bonds # This is not the easiest case, but it's arguably the most important. # For alignment purposes we can assume bonds are single. # (Due to monovalent atoms being baggage, that doesn't mean the baggage # atoms are equivalent to each other.) # # (3, 1, 2) -- e.g. N(sp3) with 1 bondpoint and 2 real bonds; # use same code and coefs, but pretend a phantom baggage atom is present if len_baggage == 1: # (3,1,2) extra = 1 if debugprints: print "debug repos baggage: sp3,1,2" plane = cross( othervecs[0], othervecs[1] ) if vlen(plane) < 0.001: # othervecs are nearly parallel (same or opposite); # could force existing bonds perp to them, at correct angle, # as close to existing posns as you can, but this case can be left # nim for now since it's rare and probably transient. if debugprints: print "debug repos baggage: othervecs are nearly parallel; " \ "this case is nim", self, other ###@@@ return plane = norm(plane) back = norm(othervecs[0] + othervecs[1]) res = [coef1 * back + coef2 * plane, coef1 * back - coef2 * plane] pass # fall thru to assigning res vecs to specific baggage elif algchoice == (3, 1, 3): back = norm(othervecs[0] + othervecs[1] + othervecs[2]) if back: res = [-back] ##e might not be as good as averaging the three crossproducts, # after choosing their sign close to -back; or something better, # since real goal is just "be repelled from them all"; # consider case where two othervecs are similar ###@@@ else: plane0 = norm( cross( othervecs[0], othervecs[1] )) if plane0: if debugprints: print "debug repos baggage: sp3 with 3 real bonds in a plane" # pick closest of plane0, -plane0 to existing posn ## # one way: ## if dot(plane0, bagvecs[0]) < 0: ## res = [-plane0] ## else: ## res = [plane0] # another way: res = [-plane0, plane0] extra = 1 else: # not possible -- if othervecs[0], othervecs[1] are antiparallel, # overall sum (in back) can't be zero; if parallel, ditto. print "can't happen: back and plane0 vanish", othervecs return pass pass elif algchoice == (2, 1, 2): # e.g. C(sp2) with 1 bondpoint and 2 real bonds back = norm(othervecs[0] + othervecs[1]) if back: res = [-back] # tested else: # real bonds are antiparallel; find closest point on equator to # existing posn, or arb point on equator p0 = cross( bagvecs[0], othervecs[0] ) if debugprints: print "debug repos baggage: antiparallel sp2 1 2 case, " \ "not p0 == %r" % (not p0) # untested so far if not p0: # bagvec is parallel too res = [arbitrary_perpendicular(othervecs[0])] else: # choose closest perpendicular to existing direction res0 = - norm( cross(p0, othervecs[0]) ) #k this ought to be positive of, but might be (buggily) # negative of, desired value -- need to test this ###@@@ # but being too lazy to test it, just make it work either way: res = [res0, -res0] extra = 1 pass pass elif algchoice == (2, 2, 1): # This only matters for twisting a pi_bond, and we verified above that # we have >single bond. A difficulty: how can we update the geometry, # not knowing whether the caller moved all the source atoms yet, # and with the bond code not knowing which direction along the bond # effects are propogating? # BTW, I guess that when you drag singlets, depositMode implems this # (along sp_chains too), but when you move chain atoms (let alone # their neighbors), I just don't recall. if debugprints: print "debug repos baggage: sp2 with twisting pi_bond is nim", self ###@@@ return else: #bruce 080515 bugfix: fallback case # (otherwise following code has UnboundLocalError for 'res') print "bug?: reposition_baggage (for %r) can't yet handle this algchoice:" % self, algchoice return # now work out the best assignment of posns in res to baggage; reorder res # to fit bags_ordered assert len(res) == len_baggage + extra bags_ordered = baggage # in case len(res) == 1 if len(res) > 1: dists = [] for atom_junk, vec, i in zip(baggage, bagvecs, range(len_baggage)): for pos in res: dists.append(( vlen(pos - vec), i, pos )) dists.sort() res0 = res res = [] bags_ordered = [] bagind_matched = [0 for bag in baggage] for dist, bagind, pos in dists: # assume not yet done matching, and don't yet know if bagind or pos # are still in the running; # when a bag matches, set bagind_matched[bagind]; # when a pos matches, remove it from res0. if bagind_matched[bagind] or pos not in res0: continue # found a match res0.remove(pos) bagind_matched[bagind] = 1 res.append(pos) bags_ordered.append(baggage[bagind]) if len(bags_ordered) >= len_baggage: break assert len(bags_ordered) == len_baggage, \ "somehow failed to match up some baggage at all, should never happen" assert len_baggage == len(res) # whether or not extra > 0 # now move the atoms, preserving distance from self # (assume calling code got that part right) for atom, vec in zip(bags_ordered, res): dist = vlen( selfposn - atom.posn() ) if abs(1.0 - vlen(vec)) > 0.00001: print "bug in reposition_baggage: vec not len 1:", vec atom.setposn( selfposn + norm(vec) * dist ) # norm(vec) is hoped to slightly reduce accumulated # numerical errors... ###e ideally we'd correct the bond lengths too, but as of 060630, # even Build doesn't get them right (and it can't, unless bond tools # would also change them when at most one real atom would need # moving, which I suppose they ought to...) if debugprints and 0: print "done" return # from _reposition_baggage_1
def _updateBreakSitesForStrand(self, strand): """ """ basesBeforeNextBreak = self.command.getNumberOfBasesBeforeNextBreak() rawStrandAtomList = strand.get_strand_atoms_in_bond_direction() strandAtomList = filter(lambda atm: not atm.is_singlet(), rawStrandAtomList) if len(strandAtomList) < 3: return #skip this strand. It doesn't have enough bases if len(strandAtomList) < basesBeforeNextBreak: return #First create a dict to store the information about the bond #that will be stored in the atom pairs. atomPair_dict = {} # ============================================= #If start and end atoms are specified between which the break sites #need to be computed -- startAtom = None endAtom = None startAtomIndex = 0 endAtomIndex = len(strandAtomList) - 1 if self._startAtoms_dict.has_key(strand): startAtom = self._startAtoms_dict[strand] #@@BUG METHOD NOT FINISHED YET #-- sometimes it gives error x not in list after breaking #a strand etc. CHECK this -- Ninad 2008-07-02 startAtomIndex = strandAtomList.index(startAtom) if self._endAtoms_dict.has_key(strand): endAtom = self._endAtoms_dict.index(endAtom) endAtomIndex = strandAtomList.index(endAtom) if startAtom and endAtom: if startAtomIndex > endAtomIndex: strandAtomList.reverse() startAtomIndex = strandAtomList.index(startAtom) endAtomIndex = strandAtomList.index(endAtom) # ============================================= i = 1 listLength = len(strandAtomList[startAtomIndex:endAtomIndex + 1]) for atm in strandAtomList[startAtomIndex:endAtomIndex + 1]: #Add '1' to the following actual atom index within the list. #This is done because the start atom itself will be counted #as '1st base atom to start with -- INCLUDING THAT ATOM when we #mark the break sites. #Example: User want to create a break after every 1 base. #So, if we start at 5' end, the 5' end atom will be considred #the first base, and a bond between the 5'end atom and the next #strand atom will be a 'break site'. Then the next break site #will be the bond '1 base after that strand atom and like that idx = strandAtomList.index(atm) if (i % basesBeforeNextBreak) == 0 and i < listLength: next_atom = strandAtomList[idx + 1] bond = find_bond(atm, next_atom) if not atomPair_dict.has_key(bond): atomPair_dict[bond] = (atm, next_atom) if DEBUG_DRAW_SPHERES_AROUND_ATOMS_AT_BREAK_SITES: for a in (atm, next_atom): if not self._breakSitesDict.has_key(id(a)): self._breakSitesDict[id(a)] = a i += 1 self._breakSites_atomPairs_dict[strand] = atomPair_dict
def _kill_Pl_and_rebond_neighbors_0(atom): # Note: we optimize for the common case (nothing wrong, conversion happens) ### NOTE: many of the following checks have probably also been done by # calling code before we get here. Optimize this sometime. [bruce 080408] bonds = atom.bonds # change these during the loop bad = False saw_plus = saw_minus = False num_bondpoints = 0 neighbors = [] direction = None # KLUGE: set this during loop, but use it afterwards too for bond in bonds: other = bond.other(atom) neighbors += [other] element = other.element direction = bond.bond_direction_from(atom) if direction == 1: saw_plus = True elif direction == -1: saw_minus = True if element is Singlet: num_bondpoints += 1 elif element.symbol in ('Ss3', 'Ss5'): # [in the 080408 copy, will often be one of each!] pass else: bad = True continue if not (len(bonds) == 2 and saw_minus and saw_plus and num_bondpoints < 2): bad = True if bad: summary_format = \ "Warning: dna updater left [N] Pl5 pseudoatom(s) unconverted" env.history.deferred_summary_message( redmsg(summary_format) ) # orange -> red [080408] return del saw_plus, saw_minus, num_bondpoints, bad # Now we know it is either Ss-Pl-Ss or X-Pl-Ss, # with fully set and consistent bond_directions. # But we'd better make sure the neighbors are not already bonded! # # (This is weird enough to get its own summary message, which is red. # Mild bug: we're not also counting it in the above message.) # # (Note: there is potentially slow debug code in rebond which is # redundant with this. It does a few other things too that we don't # need, so if it shows up in a profile, just write a custom version # for this use. ### OPTIM) n0, n1 = neighbors del neighbors b0, b1 = bonds del bonds # it might be mutable and we're changing it below, # so be sure not to use it again if find_bond(n0, n1): summary_format = \ "Error: dna updater noticed [N] Pl5 pseudoatom(s) whose neighbors are directly bonded" env.history.deferred_summary_message( redmsg(summary_format) ) return # Pull out the Pl5 and directly bond its neighbors, # reusing one of the bonds for efficiency. # (This doesn't preserve its bond_direction, so set that again.) # Kluge: the following code only works for n1 not a bondpoint # (since bond.bust on an open bond kills the bondpoint), # and fixing that would require inlining and modifying a # few Atom methods, # so to avoid this case, reverse everything if needed. if n1.element is Singlet: direction = - direction n0, n1 = n1, n0 b0, b1 = b1, b0 # Note: bonds.reverse() might modify atom.bonds itself, # so we shouldn't do it even if we didn't del bonds above. # (Even though no known harm comes from changing an atom's # order of its bonds. It's not reviewed as a problematic # change for an undo snapshot, though. Which is moot here # since we're about to remove them all. But it still seems # safer not to do it.) pass ## # save atom_posn before modifying atom (not known to be needed), and # set atom.atomtype to avoid bugs in reguess_atomtype during atom.kill # (need to do that when it still has the right number of bonds, I think) ## atom_posn = atom.posn() atom.atomtype # side effect: set atomtype old_nbonds_neighbor1 = len(n1.bonds) # for assert old_nbonds_neighbor0 = len(n0.bonds) # for assert b1.bust(make_bondpoints = False) # n1 is now missing one bond; so is atom # note: if n1 was a Singlet, this would kill it (causing bugs); # see comment above, where we swap n1 and n0 if needed to prevent that. b0.rebond(atom, n1) # now n1 has enough bonds again; atom is missing both bonds assert len(atom.bonds) == 0, "Pl %r should have no bonds but has %r" % (atom, atom.bonds) assert not atom.killed() assert len(n1.bonds) == old_nbonds_neighbor1 assert len(n0.bonds) == old_nbonds_neighbor0 ## # KLUGE: we know direction is still set to the direction of b1 from atom ## # (since b1 was processed last by the for loop above), ## # which is the overall direction from n0 thru b0 to atom thru b1 to n1, ## # so use this to optimize recording the Pl info below. ## # (Of course we really ought to just rewrite this whole conversion in Pyrex.) ## ## ## assert direction == b1.bond_direction_from(atom) # too slow to enable by default ## ## # not needed, rebond preserves it: ## ## b0.set_bond_direction_from(n0, direction) ## ## assert b0.bond_direction_from(n0) == direction # too slow to enable by default ## ## # now save the info we'll need later (this uses direction left over from for-loop) ## ## if n0.element is not Singlet: ## _save_Pl_info( n0, direction, atom_posn) ## ## if n1.element is not Singlet: ## _save_Pl_info( n1, - direction, atom_posn) # note the sign on direction # get the Pl atom out of the way atom.kill() ## # (let's hope this happened before an Undo checkpoint ever saw it -- ## # sometime verify that, and optimize if it's not true) if 0: # for now; bruce 080413 356pm # summarize our success -- we'll remove this when it becomes the default, # or condition it on a DEBUG_DNA_UPDATER flag ### debug_flags.DEBUG_DNA_UPDATER # for use later summary_format = \ "Note: dna updater removed [N] Pl5 pseudoatom(s) while converting to PAM3+5" env.history.deferred_summary_message( graymsg(summary_format) ) return
def make_or_remove_crossover(twoPls, make = True, cmdname = None): """ Make or Remove (according to make option) a crossover, given Pl5_recognizers for its two Pl atoms. """ # What we are doing is recognizing one local structure and replacing it with another # made from the same atoms. It'd sure be easier if I could do the manipulation in an mmp file, # save that somewhere, and read those to generate the operation! I'd have two sets of atoms, before and after, # and see how bonds and atomtypes got changed. # In this case it's not too hard to hand-code... I guess only the Pl atoms and their bonds are affected. # We probably do have to look at strand directions -- hmm, probably we should require them to exist before saying it's ok! # Or maybe better to give history error message when the command is chosen, saying you need to set them (or repair them) first... # Then we have to move the new/moved Pl atoms into a good position... # Note: Pl.ordered_bases are ordered by bond direction, to make this easier... # but if we want to patch up the directions in the end, do we need to care exactly which ones were defined? # or only "per-Pl"? hmm... it's per-Pl for now assert cmdname for pl in twoPls: if pl.ordered_bases is None: # should no longer be possible -- now checked before menu commands are offered [bruce 070604] ###BUG: this could have various causes, not only the one reported below! Somehow we need access to the # message supplied to the RecognizerError, for use here. ###REVIEW: Does that mean it should pass through compute methods (probably in a controlled way) # rather than making computed values None? # Or, should the value not be None, but a "frozen" examinable and reraisable version of the error exception?? msg = "%s: Error: bond direction is locally undefined or inconsistent around %s" % (cmdname, pl.atom) ###UNTESTED print "should no longer be possible:", msg #bruce 070604 env.history.message( redmsg( quote_html( msg))) return Pl1, Pl2 = twoPls a,b = Pl1.ordered_bases d,c = Pl2.ordered_bases # note: we use d,c rather than c,d so that the atom arrangement is as shown in the diagram below. # Note: for either the Make or Remove operation, the geometric arrangement is initially: # # c <-- Pl2 <-- d # # a --> Pl1 --> b # # and it ends up being (where dots indicate arrowheads, to show bond direction): # # c d # . / # \ . # Pl1 Pl2 # . \ # / . # a b # # Note: Pl1 stays attached to a, and Pl2 to d. Which two opposite bonds to preserve like that # is an arbitrary choice -- as long as Make and Remove make the same choice about that, # they'll reverse each other's effects precisely (assuming the sugars were initially correct as Ss or Sj). # break the bonds we no longer want for obj1, obj2 in [(Pl1, b), (Pl2, c)]: bond = find_bond(obj1.atom, obj2.atom) bond.bust(make_bondpoints = False) # make the bonds we want and didn't already have for obj1, obj2 in [(Pl1, c), (Pl2, b)]: assert not atoms_are_bonded(obj1.atom, obj2.atom) ###e we should make bond_atoms do this assert itself, or maybe tolerate it (or does it already??) bond_atoms_faster(obj1.atom, obj2.atom, V_SINGLE) # set directions of all 4 bonds (even the preserved ones -- it's possible they were not set before, # if some but not all bonds had directions set in the part of a strand whose directions we look at.) for obj1, obj2 in [(a, Pl1), (Pl1, c), (d, Pl2), (Pl2, b)]: bond = find_bond(obj1.atom, obj2.atom) bond.set_bond_direction_from(obj1.atom, 1) # WARNING: after that bond rearrangement, don't use our Pl5_recognizers in ways that depend on Pl bonding, # since it's not well defined whether they think about the old or new bonding to give their answers. Pl_atoms = Pl1.atom, Pl2.atom del Pl1, Pl2, twoPls # transmute base sugars to Sj or Ss as appropriate if dna_updater_is_enabled(): want = Element_Ss5 #bruce 080320 bugfix else: want = make and Element_Sj5 or Element_Ss5 for obj in (a,b,c,d): obj.atom.Transmute(want) # Note: we do this after the bond making/breaking so it doesn't add singlets which mess us up. # move Pl atoms into better positions # (someday, consider using local minimize; for now, just place them directly between their new neighbor atoms, # hopefully we leave them selected so user can easily do their own local minimize.) for pl in Pl_atoms: pl.setposn( average_value( map( lambda neighbor: neighbor.posn() , pl.neighbors() ))) env.history.message( greenmsg( cmdname + ": ") + quote_html("(%s - %s)" % tuple(Pl_atoms))) #e need assy.changed()? evidently not. return # from make_or_remove_crossover
def _convert_Pl5(atom): """ If atom's neighbors have the necessary structure (Ss-Pl-Ss or X-Pl-Ss, with fully set and consistent bond_directions), save atom's coordinates on its Ss neighbors, arrange for them to postprocess that info later, and then kill atom, replacing its bonds with a direct bond between its neighbors (same bond_direction). Summarize results (ok or error) to history. """ ### NOTE: the code starting from here has been copied and modified #### into a new function in pam3plus5_ops.py by bruce 080408. assert atom.element is Pl5 # remove when works # could also assert no dna updater error # Note: we optimize for the common case (nothing wrong, conversion happens) bonds = atom.bonds # change these during the loop bad = False saw_plus = saw_minus = False num_bondpoints = 0 neighbors = [] direction = None # KLUGE: set this during loop, but use it afterwards too for bond in bonds: other = bond.other(atom) neighbors += [other] element = other.element direction = bond.bond_direction_from(atom) if direction == 1: saw_plus = True elif direction == -1: saw_minus = True if element is Singlet: num_bondpoints += 1 elif element.symbol in ('Ss3', 'Ss5'): pass else: bad = True continue if not (len(bonds) == 2 and saw_minus and saw_plus and num_bondpoints < 2): bad = True if bad: summary_format = \ "Warning: dna updater left [N] Pl5 pseudoatom(s) unconverted" env.history.deferred_summary_message( orangemsg(summary_format) ) return del saw_plus, saw_minus, num_bondpoints, bad # Now we know it is either Ss-Pl-Ss or X-Pl-Ss, # with fully set and consistent bond_directions. # But we'd better make sure the neighbors are not already bonded! # # (This is weird enough to get its own summary message, which is red. # Mild bug: we're not also counting it in the above message.) # # (Note: there is potentially slow debug code in rebond which is # redundant with this. It does a few other things too that we don't # need, so if it shows up in a profile, just write a custom version # for this use. ### OPTIM) n0, n1 = neighbors del neighbors b0, b1 = bonds del bonds # it might be mutable and we're changing it below, # so be sure not to use it again if find_bond(n0, n1): summary_format = \ "Error: dna updater noticed [N] Pl5 pseudoatom(s) whose neighbors are directly bonded" env.history.deferred_summary_message( redmsg(summary_format) ) return # Pull out the Pl5 and directly bond its neighbors, # reusing one of the bonds for efficiency. # (This doesn't preserve its bond_direction, so set that again.) # Kluge: the following code only works for n1 not a bondpoint # (since bond.bust on an open bond kills the bondpoint), # and fixing that would require inlining and modifying a # few Atom methods, # so to avoid this case, reverse everything if needed. if n1.element is Singlet: direction = - direction n0, n1 = n1, n0 b0, b1 = b1, b0 # Note: bonds.reverse() might modify atom.bonds itself, # so we shouldn't do it even if we didn't del bonds above. # (Even though no known harm comes from changing an atom's # order of its bonds. It's not reviewed as a problematic # change for an undo snapshot, though. Which is moot here # since we're about to remove them all. But it still seems # safer not to do it.) pass # save atom_posn before modifying atom (not known to be needed), # and set atom.atomtype to avoid bugs in reguess_atomtype during atom.kill # (need to do that when it still has the right number of bonds, I think) atom_posn = atom.posn() atom.atomtype # side effect: set atomtype old_nbonds_neighbor1 = len(n1.bonds) # for assert old_nbonds_neighbor0 = len(n0.bonds) # for assert b1.bust(make_bondpoints = False) # n1 is now missing one bond; so is atom b0.rebond(atom, n1) # now n1 has enough bonds again; atom is missing both bonds assert len(atom.bonds) == 0, "Pl %r should have no bonds but has %r" % (atom, atom.bonds) assert not atom.killed() assert len(n1.bonds) == old_nbonds_neighbor1 assert len(n0.bonds) == old_nbonds_neighbor0 # KLUGE: we know direction is still set to the direction of b1 from atom # (since b1 was processed last by the for loop above), # which is the overall direction from n0 thru b0 to atom thru b1 to n1, # so use this to optimize recording the Pl info below. # (Of course we really ought to just rewrite this whole conversion in Pyrex.) ## assert direction == b1.bond_direction_from(atom) # too slow to enable by default # not needed, rebond preserves it: ## b0.set_bond_direction_from(n0, direction) ## assert b0.bond_direction_from(n0) == direction # too slow to enable by default # now save the info we'll need later (this uses direction left over from for-loop) if n0.element is not Singlet: _save_Pl_info( n0, direction, atom_posn) if n1.element is not Singlet: _save_Pl_info( n1, - direction, atom_posn) # note the sign on direction # get the Pl atom out of the way atom.kill() # (let's hope this happened before an Undo checkpoint ever saw it -- # sometime verify that, and optimize if it's not true) # summarize our success -- we'll remove this when it becomes the default, # or condition it on a DEBUG_DNA_UPDATER flag ### debug_flags.DEBUG_DNA_UPDATER # for use later summary_format = \ "Note: dna updater converted [N] Pl5 pseudoatom(s)" env.history.deferred_summary_message( graymsg(summary_format) ) return
def _reposition_baggage_1(self, baggage, other, planned_atom_nupos): """ """ # trivial cases len_baggage = len(baggage) if not len_baggage: return # cases handled well enough by calling code (as of 060629), # needing no correction here len_other = len(self.bonds) - len_baggage if not len_other: # should never happen, as we are called as of 060629, i think, # though if it did, there would be things we could do in theory, # like rotate atomtype.bondvecs to best match baggage... print "bug?: %r.reposition_baggage(%r) finds no other atoms -- " \ "nim, not thought to ever happen" % (self, baggage) return if len_other == 1: # the Q(old, new) code in the callers ought to handle it properly -- # UNLESS other is a pi_bond, and its alignment ought to affect a pair # of baggage atoms. if self.atomtype.spX == 2: # note: true for sp2 and sp2(graphitic) pass # let the main code run, including some other # "if len_other == 1" scattered around ##e someday: don't we want to also notice sp, and propogate # twisting of a pi_bond through an sp_chain? # I recall some code in depositMode for that... # I can't remember its scope, thus whether it covers this already... # I think not. ###@@@ else: return # at least 2 other (except sp2 pi_bond other), and at least one baggage... # might as well get other_posns we'll want to use # (handling planned_atom_nupos once and for all). if other == -1: other = [] baggage_keys = [atom.key for atom in baggage] for b in self.bonds: atom = b.other(self) if atom.key not in baggage_keys: other.append(atom) if len(other) != len_other: # must mean baggage is not a subset of neighbors args = (self, baggage, planned_atom_nupos) print "bug in reposition_baggage%r: len(other == %r) != len_other %r" % \ (args, other, len_other) return if len_other == 1: other0_bond = find_bond(other[0], self) if other0_bond.v6 == V_SINGLE: # calling code handled this case well enough return planned_atom, nupos = None, None if planned_atom_nupos: planned_atom, nupos = planned_atom_nupos if planned_atom not in other: print "likely bug in reposition_baggage: " \ "planned_atom not in other", planned_atom, other other_posns = [(atom.posn(), nupos)[atom is planned_atom] for atom in other] #e we might later wish we'd kept a list of the bonds to baggage and # other, to grab the v6's -- make a dict from atom.key above? selfposn = self.posn() othervecs = [norm(pos - selfposn) for pos in other_posns] bag_posns = [atom.posn() for atom in baggage] bagvecs = [norm(pos - selfposn) for pos in bag_posns] # The alg is specific to atomtype, and number and sometimes *type* of all # bonds involved. We'll code the most important and/or easiest cases first. # Someday we might move them into methods of the atomtypes themselves. algchoice = (self.atomtype.spX, len_baggage, len_other) # len_baggage >= 1, len_other >= 2 (except pi_bond case) extra = 0 # might be altered below if algchoice == (3, 2, 2) or algchoice == (3, 1, 2): # (3, 2, 2) -- e.g. C(sp3) with 2 bp's and 2 real bonds # This is not the easiest case, but it's arguably the most important. # For alignment purposes we can assume bonds are single. # (Due to monovalent atoms being baggage, that doesn't mean the baggage # atoms are equivalent to each other.) # # (3, 1, 2) -- e.g. N(sp3) with 1 bondpoint and 2 real bonds; # use same code and coefs, but pretend a phantom baggage atom is present if len_baggage == 1: # (3,1,2) extra = 1 if debugprints: print "debug repos baggage: sp3,1,2" plane = cross(othervecs[0], othervecs[1]) if vlen(plane) < 0.001: # othervecs are nearly parallel (same or opposite); # could force existing bonds perp to them, at correct angle, # as close to existing posns as you can, but this case can be left # nim for now since it's rare and probably transient. if debugprints: print "debug repos baggage: othervecs are nearly parallel; " \ "this case is nim", self, other ###@@@ return plane = norm(plane) back = norm(othervecs[0] + othervecs[1]) res = [coef1 * back + coef2 * plane, coef1 * back - coef2 * plane] pass # fall thru to assigning res vecs to specific baggage elif algchoice == (3, 1, 3): back = norm(othervecs[0] + othervecs[1] + othervecs[2]) if back: res = [-back] ##e might not be as good as averaging the three crossproducts, # after choosing their sign close to -back; or something better, # since real goal is just "be repelled from them all"; # consider case where two othervecs are similar ###@@@ else: plane0 = norm(cross(othervecs[0], othervecs[1])) if plane0: if debugprints: print "debug repos baggage: sp3 with 3 real bonds in a plane" # pick closest of plane0, -plane0 to existing posn ## # one way: ## if dot(plane0, bagvecs[0]) < 0: ## res = [-plane0] ## else: ## res = [plane0] # another way: res = [-plane0, plane0] extra = 1 else: # not possible -- if othervecs[0], othervecs[1] are antiparallel, # overall sum (in back) can't be zero; if parallel, ditto. print "can't happen: back and plane0 vanish", othervecs return pass pass elif algchoice == (2, 1, 2): # e.g. C(sp2) with 1 bondpoint and 2 real bonds back = norm(othervecs[0] + othervecs[1]) if back: res = [-back] # tested else: # real bonds are antiparallel; find closest point on equator to # existing posn, or arb point on equator p0 = cross(bagvecs[0], othervecs[0]) if debugprints: print "debug repos baggage: antiparallel sp2 1 2 case, " \ "not p0 == %r" % (not p0) # untested so far if not p0: # bagvec is parallel too res = [arbitrary_perpendicular(othervecs[0])] else: # choose closest perpendicular to existing direction res0 = -norm(cross(p0, othervecs[0])) #k this ought to be positive of, but might be (buggily) # negative of, desired value -- need to test this ###@@@ # but being too lazy to test it, just make it work either way: res = [res0, -res0] extra = 1 pass pass elif algchoice == (2, 2, 1): # This only matters for twisting a pi_bond, and we verified above that # we have >single bond. A difficulty: how can we update the geometry, # not knowing whether the caller moved all the source atoms yet, # and with the bond code not knowing which direction along the bond # effects are propogating? # BTW, I guess that when you drag singlets, depositMode implems this # (along sp_chains too), but when you move chain atoms (let alone # their neighbors), I just don't recall. if debugprints: print "debug repos baggage: sp2 with twisting pi_bond is nim", self ###@@@ return else: #bruce 080515 bugfix: fallback case # (otherwise following code has UnboundLocalError for 'res') print "bug?: reposition_baggage (for %r) can't yet handle this algchoice:" % self, algchoice return # now work out the best assignment of posns in res to baggage; reorder res # to fit bags_ordered assert len(res) == len_baggage + extra bags_ordered = baggage # in case len(res) == 1 if len(res) > 1: dists = [] for atom_junk, vec, i in zip(baggage, bagvecs, range(len_baggage)): for pos in res: dists.append((vlen(pos - vec), i, pos)) dists.sort() res0 = res res = [] bags_ordered = [] bagind_matched = [0 for bag in baggage] for dist, bagind, pos in dists: # assume not yet done matching, and don't yet know if bagind or pos # are still in the running; # when a bag matches, set bagind_matched[bagind]; # when a pos matches, remove it from res0. if bagind_matched[bagind] or pos not in res0: continue # found a match res0.remove(pos) bagind_matched[bagind] = 1 res.append(pos) bags_ordered.append(baggage[bagind]) if len(bags_ordered) >= len_baggage: break assert len(bags_ordered) == len_baggage, \ "somehow failed to match up some baggage at all, should never happen" assert len_baggage == len(res) # whether or not extra > 0 # now move the atoms, preserving distance from self # (assume calling code got that part right) for atom, vec in zip(bags_ordered, res): dist = vlen(selfposn - atom.posn()) if abs(1.0 - vlen(vec)) > 0.00001: print "bug in reposition_baggage: vec not len 1:", vec atom.setposn(selfposn + norm(vec) * dist) # norm(vec) is hoped to slightly reduce accumulated # numerical errors... ###e ideally we'd correct the bond lengths too, but as of 060630, # even Build doesn't get them right (and it can't, unless bond tools # would also change them when at most one real atom would need # moving, which I suppose they ought to...) if debugprints and 0: print "done" return # from _reposition_baggage_1
def _updateBreakSitesForStrand(self, strand): """ """ basesBeforeNextBreak = self.command.getNumberOfBasesBeforeNextBreak() rawStrandAtomList = strand.get_strand_atoms_in_bond_direction() strandAtomList = filter(lambda atm: not atm.is_singlet(), rawStrandAtomList) if len(strandAtomList) < 3: return #skip this strand. It doesn't have enough bases if len(strandAtomList) < basesBeforeNextBreak: return #First create a dict to store the information about the bond #that will be stored in the atom pairs. atomPair_dict = {} # ============================================= #If start and end atoms are specified between which the break sites #need to be computed -- startAtom = None endAtom = None startAtomIndex = 0 endAtomIndex = len(strandAtomList) - 1 if self._startAtoms_dict.has_key(strand): startAtom = self._startAtoms_dict[strand] #@@BUG METHOD NOT FINISHED YET #-- sometimes it gives error x not in list after breaking #a strand etc. CHECK this -- Ninad 2008-07-02 startAtomIndex = strandAtomList.index(startAtom) if self._endAtoms_dict.has_key(strand): endAtom = self._endAtoms_dict.index(endAtom) endAtomIndex = strandAtomList.index(endAtom) if startAtom and endAtom: if startAtomIndex > endAtomIndex: strandAtomList.reverse() startAtomIndex = strandAtomList.index(startAtom) endAtomIndex = strandAtomList.index(endAtom) # ============================================= i = 1 listLength = len(strandAtomList[startAtomIndex: endAtomIndex + 1]) for atm in strandAtomList[startAtomIndex: endAtomIndex + 1]: #Add '1' to the following actual atom index within the list. #This is done because the start atom itself will be counted #as '1st base atom to start with -- INCLUDING THAT ATOM when we #mark the break sites. #Example: User want to create a break after every 1 base. #So, if we start at 5' end, the 5' end atom will be considred #the first base, and a bond between the 5'end atom and the next #strand atom will be a 'break site'. Then the next break site #will be the bond '1 base after that strand atom and like that idx = strandAtomList.index(atm) if (i%basesBeforeNextBreak) == 0 and i < listLength: next_atom = strandAtomList[idx + 1] bond = find_bond(atm, next_atom) if not atomPair_dict.has_key(bond): atomPair_dict[bond] = (atm, next_atom) if DEBUG_DRAW_SPHERES_AROUND_ATOMS_AT_BREAK_SITES: for a in (atm, next_atom): if not self._breakSitesDict.has_key(id(a)): self._breakSitesDict[id(a)] = a i += 1 self._breakSites_atomPairs_dict[strand] = atomPair_dict