Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
 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)
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
    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