def _f_find_new_ladder_location_of_baseatom(self):
    # note: used in this file and in PAM_Atom_methods
    """
    param self: a PAM atom. [#doc more]
    """
    #bruce 080411 split common code out of several methods,
    # then totally rewrote it to stop assuming wrongly
    # that atom.molecule.ladder can find fresh ladders
    # that didn't yet remake their chunks

    locator = _f_atom_to_ladder_location_dict
    data = locator.get(self.key)
    if data:
        return data # (ladder, whichrail, index)
    # otherwise it must be an end atom on a non-fresh ladder
    ladder = rail_end_atom_to_ladder(self)
    whichrail, index = ladder.whichrail_and_index_of_baseatom(self)
        # by search in ladder, optimized to try the ends first
    return ladder, whichrail, index
Exemple #2
0
def _f_find_new_ladder_location_of_baseatom(self):
    # note: used in this file and in PAM_Atom_methods
    """
    param self: a PAM atom. [#doc more]
    """
    #bruce 080411 split common code out of several methods,
    # then totally rewrote it to stop assuming wrongly
    # that atom.molecule.ladder can find fresh ladders
    # that didn't yet remake their chunks

    locator = _f_atom_to_ladder_location_dict
    data = locator.get(self.key)
    if data:
        return data  # (ladder, whichrail, index)
    # otherwise it must be an end atom on a non-fresh ladder
    ladder = rail_end_atom_to_ladder(self)
    whichrail, index = ladder.whichrail_and_index_of_baseatom(self)
    # by search in ladder, optimized to try the ends first
    return ladder, whichrail, index
Exemple #3
0
    def _f_update_neighbor_baseatoms(self):
        """
        [friend method for dna updater]

        This must be called at least once per ladder rail chain
        (i.e. _DnaChainFragment object, I think, 080116),
        during each dna updater run which encounters it (whether as a
        new or preexisting rail chain).

        It computes or recomputes whichever attributes carry info about
        the neighboring baseatoms in neighboring rail chains (which
        connect to self at the ends), based on current bonding.

        Specifically, it sets self.neighbor_baseatoms[end] for end in LADDER_ENDS
        to either None (if this chain ends on that ladder-end) or to the
        next atom in the next chain (if it doesn't end). (Atoms with
        _dna_updater__error set are not allowed as "next atoms" -- None will
        be set instead.)

        For end atom order (used as index in self.neighbor_baseatoms),
        it uses whatever order has been established by the DnaLadder
        we're in, which may or may not have reversed our order. (Ladders
        have been made, finished, and merged, before we're called.)

        For strands, it finds neighbors using bond direction, and knows
        about skipping Pl atoms; for axes, in the ambiguous length==1 case,
        it uses an arbitrary order, but
        makes sure this is consistent with strands when at least one strand
        has no nick at one end of this chain's ladder. [#todo: explain better]
        If this doesn't force an order, then if this had already been set
        before this call and either of the same non-None atoms are still
        in it now, preserve their position.

        The attrs we set are subsequently reversed by our methods
        _f_reverse_arbitrary_bond_direction and reverse_baseatoms.
        [###REVIEW whether it's correct in _f_reverse_arbitrary_bond_direction;
        see comment there.]

        @note: the ordering/reversal scheme described above may need
        revision. The special case for length==1 axis described above is
        meant to ensure
        that axis and strand order correspond between two ladders which
        are connected on the axis and one strand, but not the other strand
        (i.e. which have an ordinary nick), if the ladder we're on has length 1
        (i.e. if there are two nicks in a row, on the same or opposite strands).
        If this ever matters, we might need to straighten out this order
        in DnaLadder.finished() for length==1 ladders. The ladders are already
        made and merged by the time we're called, so whatever reversals they'll
        do are already done. [update, 080602: this was implemented today,
        to fix a bug; no change to DnaLadder.finished() seemed to be needed.]
        """
        assert self.strandQ in (False, True)
        self.neighbor_baseatoms = list(self.neighbor_baseatoms)
        # do most atoms one end at a time...
        for end in LADDER_ENDS:  # end_baseatoms needs ladder end, not chain end
            next_atom = -1  # will be set to None or an atom, if possible
            if self.strandQ:
                # similar to code in DnaLadder._can_merge_at_end
                end_atom = self.end_baseatoms()[end]
                assert self._bond_direction
                # relative to LADDER_ENDS directions
                # (since that's in the same direction as our baseatoms array)
                # (review: not 100% sure this is set yet;
                #  but the assert is now old and has never failed)
                if end == LADDER_END0:
                    arrayindex_dir_to_neighbor = -1
                else:
                    arrayindex_dir_to_neighbor = 1
                bond_dir_to_neighbor = self._bond_direction * arrayindex_dir_to_neighbor
                next_atom = end_atom.strand_next_baseatom(
                    bond_direction=bond_dir_to_neighbor)
                assert next_atom is None or next_atom.element.role == 'strand'
                # (note: strand_next_baseatom returns None if end_atom or the atom it
                #  might return has ._dna_updater__error set.)
                # store next_atom at end of loop
                if len(self.baseatoms) > 1:  #080602 debug code
                    if arrayindex_dir_to_neighbor == 1:
                        next_interior_atom = self.baseatoms[1]
                    else:
                        next_interior_atom = self.baseatoms[-2]
                    if next_interior_atom is next_atom:
                        print "\n*** PROBABLE BUG: next_interior_atom is next_atom %r for end_atom %r in %r" % \
                              (next_atom, end_atom, self)
                    pass
            else:
                # do axis atoms in this per-end loop, only if chain length > 1;
                # otherwise do them both at once, after this loop.
                if len(self.baseatoms) > 1:
                    # easy case - unambiguous other-chain neighbor atom
                    # (Note: length-2 axis ring is not possible, since it would
                    #  require two bonds between the same two Ax pseudoatoms.
                    #  It's also not physically possible, so that's fine.)
                    end_atom = self.end_baseatoms()[end]
                    next_atom_candidates = end_atom.axis_neighbors(
                    )  # len 1 or 2,
                    # and one should always be the next one in this chain
                    # (I guess it can't have _dna_updater__error set,
                    #  since such atoms are not made into chains;
                    #  so I assert this, 080206);
                    # the other one (if present) might have it set
                    if end == LADDER_END0:
                        next_to_end_index = 1
                    else:
                        next_to_end_index = -2
                    not_this_atom = self.baseatoms[next_to_end_index]
                    assert not not_this_atom._dna_updater__error  # 080206
                    next_atom_candidates.remove(not_this_atom)
                    # it has to be there, so we don't mind raising an
                    # exception when it's not
                    assert len(next_atom_candidates) <= 1, \
                           "too many candidates: %r" % (next_atom_candidates,)
                    # Note: got exception here when lots of Ss had two
                    # Ax neighbors due to errors in an mmp file;
                    # assertion message untested [080304]
                    if next_atom_candidates:
                        next_atom = next_atom_candidates[0]
                        if next_atom._dna_updater__error:
                            ## print "avoiding bug 080206 by not seeing next axis atom with error", next_atom
                            next_atom = None
                    else:
                        next_atom = None
                    pass
                pass
            if next_atom != -1:
                self.neighbor_baseatoms[end] = next_atom  # None or an atom
                assert next_atom is None or not next_atom._dna_updater__error  # 080206
                if DEBUG_NEIGHBOR_BASEATOMS:
                    msg = "set %r.neighbor_baseatoms[%r] = %r, whole list is now %r" % \
                          (self, end, next_atom, self.neighbor_baseatoms)
                    print_compact_stack("\n" + msg + ": ")
            continue
        # ... but in length==1 case, do axis atoms both at once
        if not self.strandQ and len(self.baseatoms) == 1:
            end_atom = self.baseatoms[0]
            next_atoms = end_atom.axis_neighbors()  # len 0 or 1 or 2
            # remove atoms with errors [fix predicted bug, 080206]
            # (review: should axis_neighbors, or a variant method, do this?)
            ###### SHOULD REVIEW ALL USES OF axis_neighbors FOR NEEDING THIS @@@@@
            next_atoms = filter(lambda atom: not atom._dna_updater__error,
                                next_atoms)
            while len(next_atoms) < 2:
                next_atoms.append(None)

            # if order matters, reverse this here, if either strand
            # in the same ladder indicates we ought to, by its next atom
            # bonding to one of these atoms (having no nick); I think any
            # advice we get from this (from 1 of 4 possible next atoms)
            # can't be inconsistent, but I haven't proved this. (Certainly
            # it can't be for physically reasonable structures.)
            # [bruce 080116 proposed, bruce 080602 implemented, as bugfix]
            order_was_forced_by_strands = False  # might be set below
            try:
                # very near a release, so cause no new harm...
                ladder = rail_end_atom_to_ladder(end_atom)
                assert self is ladder.axis_rail
                evidence_counters = [0, 0]  # [wrong order, right order]
                for strand in ladder.strand_rails:
                    strand._f_update_neighbor_baseatoms()
                    # redundant with caller, but necessary since we don't
                    # know whether it called this already or not;
                    # calling it twice on a strand is ok (2nd call is a noop);
                    # not important to optimize since length-1 ladders are rare.
                    for strand_end in LADDER_ENDS:
                        for axis_end in LADDER_ENDS:
                            axis_next_atom = next_atoms[
                                axis_end]  # might be None
                            strand_next_atom = strand.neighbor_baseatoms[
                                strand_end]  # might be None
                            if axis_next_atom and strand_next_atom and \
                               atoms_are_bonded( axis_next_atom, strand_next_atom):
                                evidence_counters[strand_end == axis_end] += 1
                            continue
                        continue
                    continue
                badvote, goodvote = evidence_counters
                # note: current order of next_atoms is arbitrary,
                # so we need symmetry here between badvote and goodvote
                if badvote != goodvote:
                    if badvote > goodvote:
                        next_atoms.reverse()
                        badvote, goodvote = goodvote, badvote
                        pass
                    order_was_forced_by_strands = True
                if badvote and goodvote:
                    # should never happen for physically reasonable structures,
                    # but is probably possible for nonsense structures
                    print "\nBUG or unreasonable structure: " \
                          "badvote %d goodvote %d for next_atoms %r " \
                          "around %r with %r" % \
                          (badvote, goodvote, next_atoms, self, end_atom)
                    pass
                pass
            except:
                msg = "\n*** BUG: ignoring exception while disambiguating " \
                      "next_atoms %r around %r with %r" % \
                      (next_atoms, self, end_atom)
                print_compact_traceback(msg + ": ")
                pass

            reverse_count = 0  # for debug prints only

            if not order_was_forced_by_strands:
                # For stability of arbitrary choices in case self.neighbor_baseatoms
                # was already set, let non-None atoms still in it determine the order
                # to preserve their position, unless the order was forced above.
                for end in LADDER_ENDS:
                    old_atom = self.neighbor_baseatoms[end]
                    if old_atom and old_atom is next_atoms[1 - end]:
                        assert old_atom != -1  # next_atoms can't contain -1
                        next_atoms.reverse()  # (this can't happen twice)
                        reverse_count += 1

            self.neighbor_baseatoms = next_atoms

            if DEBUG_NEIGHBOR_BASEATOMS:
                msg = "set %r.neighbor_baseatoms = next_atoms %r, " \
                      "order_was_forced_by_strands = %r, reverse_count = %r" % \
                      (self, next_atoms, order_was_forced_by_strands, reverse_count)
                print_compact_stack("\n" + msg + ": ")
                pass
            pass

        # we're done
        assert len(self.neighbor_baseatoms) == 2
        assert type(self.neighbor_baseatoms) == type([])
        for atom in self.neighbor_baseatoms:
            assert atom is None or atom.element.role in ['axis', 'strand']
            # note: 'unpaired-base' won't appear in any chain
        return  # from _f_update_neighbor_baseatoms
Exemple #4
0
    def _f_update_neighbor_baseatoms(self):
        """
        [friend method for dna updater]
        
        This must be called at least once per ladder rail chain
        (i.e. _DnaChainFragment object, I think, 080116),
        during each dna updater run which encounters it (whether as a
        new or preexisting rail chain).

        It computes or recomputes whichever attributes carry info about
        the neighboring baseatoms in neighboring rail chains (which
        connect to self at the ends), based on current bonding.

        Specifically, it sets self.neighbor_baseatoms[end] for end in LADDER_ENDS
        to either None (if this chain ends on that ladder-end) or to the
        next atom in the next chain (if it doesn't end). (Atoms with
        _dna_updater__error set are not allowed as "next atoms" -- None will
        be set instead.)

        For end atom order (used as index in self.neighbor_baseatoms),
        it uses whatever order has been established by the DnaLadder
        we're in, which may or may not have reversed our order. (Ladders
        have been made, finished, and merged, before we're called.)

        For strands, it finds neighbors using bond direction, and knows
        about skipping Pl atoms; for axes, in the ambiguous length==1 case,
        it uses an arbitrary order, but
        makes sure this is consistent with strands when at least one strand
        has no nick at one end of this chain's ladder. [#todo: explain better]
        If this doesn't force an order, then if this had already been set
        before this call and either of the same non-None atoms are still
        in it now, preserve their position.
        
        The attrs we set are subsequently reversed by our methods
        _f_reverse_arbitrary_bond_direction and reverse_baseatoms.
        [###REVIEW whether it's correct in _f_reverse_arbitrary_bond_direction;
        see comment there.]
        
        @note: the ordering/reversal scheme described above may need
        revision. The special case for length==1 axis described above is
        meant to ensure
        that axis and strand order correspond between two ladders which
        are connected on the axis and one strand, but not the other strand
        (i.e. which have an ordinary nick), if the ladder we're on has length 1
        (i.e. if there are two nicks in a row, on the same or opposite strands).
        If this ever matters, we might need to straighten out this order
        in DnaLadder.finished() for length==1 ladders. The ladders are already
        made and merged by the time we're called, so whatever reversals they'll
        do are already done. [update, 080602: this was implemented today,
        to fix a bug; no change to DnaLadder.finished() seemed to be needed.]
        """
        assert self.strandQ in (False, True)
        self.neighbor_baseatoms = list(self.neighbor_baseatoms)
        # do most atoms one end at a time...
        for end in LADDER_ENDS: # end_baseatoms needs ladder end, not chain end
            next_atom = -1 # will be set to None or an atom, if possible
            if self.strandQ:
                # similar to code in DnaLadder._can_merge_at_end
                end_atom = self.end_baseatoms()[end]
                assert self._bond_direction
                    # relative to LADDER_ENDS directions
                    # (since that's in the same direction as our baseatoms array)
                    # (review: not 100% sure this is set yet;
                    #  but the assert is now old and has never failed)
                if end == LADDER_END0:
                    arrayindex_dir_to_neighbor = -1
                else:
                    arrayindex_dir_to_neighbor = 1
                bond_dir_to_neighbor = self._bond_direction * arrayindex_dir_to_neighbor
                next_atom = end_atom.strand_next_baseatom(bond_direction = bond_dir_to_neighbor)
                assert next_atom is None or next_atom.element.role == 'strand'
                # (note: strand_next_baseatom returns None if end_atom or the atom it
                #  might return has ._dna_updater__error set.)
                # store next_atom at end of loop
                if len(self.baseatoms) > 1: #080602 debug code
                    if arrayindex_dir_to_neighbor == 1:
                        next_interior_atom = self.baseatoms[1]
                    else:
                        next_interior_atom = self.baseatoms[-2]
                    if next_interior_atom is next_atom:
                        print "\n*** PROBABLE BUG: next_interior_atom is next_atom %r for end_atom %r in %r" % \
                              (next_atom, end_atom, self)
                    pass
            else:
                # do axis atoms in this per-end loop, only if chain length > 1;
                # otherwise do them both at once, after this loop.
                if len(self.baseatoms) > 1:
                    # easy case - unambiguous other-chain neighbor atom
                    # (Note: length-2 axis ring is not possible, since it would
                    #  require two bonds between the same two Ax pseudoatoms.
                    #  It's also not physically possible, so that's fine.)
                    end_atom = self.end_baseatoms()[end]
                    next_atom_candidates = end_atom.axis_neighbors() # len 1 or 2,
                        # and one should always be the next one in this chain
                        # (I guess it can't have _dna_updater__error set,
                        #  since such atoms are not made into chains;
                        #  so I assert this, 080206);
                        # the other one (if present) might have it set
                    if end == LADDER_END0:
                        next_to_end_index = 1
                    else:
                        next_to_end_index = -2
                    not_this_atom = self.baseatoms[next_to_end_index]
                    assert not not_this_atom._dna_updater__error # 080206
                    next_atom_candidates.remove(not_this_atom)
                        # it has to be there, so we don't mind raising an
                        # exception when it's not
                    assert len(next_atom_candidates) <= 1, \
                           "too many candidates: %r" % (next_atom_candidates,)
                        # Note: got exception here when lots of Ss had two
                        # Ax neighbors due to errors in an mmp file;
                        # assertion message untested [080304]
                    if next_atom_candidates:
                        next_atom = next_atom_candidates[0]
                        if next_atom._dna_updater__error:
                            ## print "avoiding bug 080206 by not seeing next axis atom with error", next_atom
                            next_atom = None
                    else:
                        next_atom = None
                    pass
                pass
            if next_atom != -1:
                self.neighbor_baseatoms[end] = next_atom # None or an atom
                assert next_atom is None or not next_atom._dna_updater__error # 080206
                if DEBUG_NEIGHBOR_BASEATOMS:
                    msg = "set %r.neighbor_baseatoms[%r] = %r, whole list is now %r" % \
                          (self, end, next_atom, self.neighbor_baseatoms)
                    print_compact_stack( "\n" + msg + ": ")
            continue
        # ... but in length==1 case, do axis atoms both at once
        if not self.strandQ and len(self.baseatoms) == 1:
            end_atom = self.baseatoms[0]
            next_atoms = end_atom.axis_neighbors() # len 0 or 1 or 2
            # remove atoms with errors [fix predicted bug, 080206]
            # (review: should axis_neighbors, or a variant method, do this?)
            ###### SHOULD REVIEW ALL USES OF axis_neighbors FOR NEEDING THIS @@@@@
            next_atoms = filter( lambda atom: not atom._dna_updater__error , next_atoms )
            while len(next_atoms) < 2:
                next_atoms.append(None)
            
            # if order matters, reverse this here, if either strand
            # in the same ladder indicates we ought to, by its next atom
            # bonding to one of these atoms (having no nick); I think any
            # advice we get from this (from 1 of 4 possible next atoms)
            # can't be inconsistent, but I haven't proved this. (Certainly
            # it can't be for physically reasonable structures.)
            # [bruce 080116 proposed, bruce 080602 implemented, as bugfix]
            order_was_forced_by_strands = False # might be set below
            try:
                # very near a release, so cause no new harm...
                ladder = rail_end_atom_to_ladder(end_atom)
                assert self is ladder.axis_rail
                evidence_counters = [0, 0] # [wrong order, right order]
                for strand in ladder.strand_rails:
                    strand._f_update_neighbor_baseatoms()
                        # redundant with caller, but necessary since we don't
                        # know whether it called this already or not;
                        # calling it twice on a strand is ok (2nd call is a noop);
                        # not important to optimize since length-1 ladders are rare.
                    for strand_end in LADDER_ENDS:
                        for axis_end in LADDER_ENDS:
                            axis_next_atom = next_atoms[axis_end] # might be None
                            strand_next_atom = strand.neighbor_baseatoms[strand_end] # might be None
                            if axis_next_atom and strand_next_atom and \
                               atoms_are_bonded( axis_next_atom, strand_next_atom):
                                evidence_counters[ strand_end == axis_end ] += 1
                            continue
                        continue
                    continue
                badvote, goodvote = evidence_counters
                    # note: current order of next_atoms is arbitrary,
                    # so we need symmetry here between badvote and goodvote
                if badvote != goodvote:
                    if badvote > goodvote:
                        next_atoms.reverse()
                        badvote, goodvote = goodvote, badvote
                        pass
                    order_was_forced_by_strands = True
                if badvote and goodvote:
                    # should never happen for physically reasonable structures,
                    # but is probably possible for nonsense structures
                    print "\nBUG or unreasonable structure: " \
                          "badvote %d goodvote %d for next_atoms %r " \
                          "around %r with %r" % \
                          (badvote, goodvote, next_atoms, self, end_atom)
                    pass
                pass
            except:
                msg = "\n*** BUG: ignoring exception while disambiguating " \
                      "next_atoms %r around %r with %r" % \
                      (next_atoms, self, end_atom)
                print_compact_traceback( msg + ": " )
                pass
            
            reverse_count = 0 # for debug prints only
            
            if not order_was_forced_by_strands:
                # For stability of arbitrary choices in case self.neighbor_baseatoms
                # was already set, let non-None atoms still in it determine the order
                # to preserve their position, unless the order was forced above.
                for end in LADDER_ENDS:
                    old_atom = self.neighbor_baseatoms[end]
                    if old_atom and old_atom is next_atoms[1-end]:
                        assert old_atom != -1 # next_atoms can't contain -1
                        next_atoms.reverse() # (this can't happen twice)
                        reverse_count += 1
            
            self.neighbor_baseatoms = next_atoms
            
            if DEBUG_NEIGHBOR_BASEATOMS:
                msg = "set %r.neighbor_baseatoms = next_atoms %r, " \
                      "order_was_forced_by_strands = %r, reverse_count = %r" % \
                      (self, next_atoms, order_was_forced_by_strands, reverse_count)
                print_compact_stack( "\n" + msg + ": ")
                pass
            pass
        
        # we're done
        assert len(self.neighbor_baseatoms) == 2
        assert type(self.neighbor_baseatoms) == type([])
        for atom in self.neighbor_baseatoms:
            assert atom is None or atom.element.role in ['axis', 'strand']
                # note: 'unpaired-base' won't appear in any chain
        return # from _f_update_neighbor_baseatoms