def bind21(reactant1, reactant2, max_helix=True, remote=None):
    """
    Returns a list of reaction pathways which can be produced by 2-1 binding
    reactions of the argument complexes. The 2-1 binding reaction is the
    hybridization of two complementary unpaired domains, each in a different
    complex, to produce a single, unpseudoknotted product complex containing
    all of the strands contained in either of the original complexes.

    Note: remote is ineffective, but may be set for convencience
    """
    r1_doms = reactant1.available_domains
    r2_doms = reactant2.available_domains

    reactions = []

    # Iterate through all the free domains in reactant1
    for (dom1, strand_num1, dom_num1) in r1_doms:
        # For each, find any domains in reactant2 that could bind
        for (dom2, strand_num2, dom_num2) in r2_doms:
            # If it can pair, this is one possible reaction (this kind of
            # reaction cannot possibly produce a pseudoknotted structure)
            if (dom1.can_pair(dom2)):
                # combine the two complexes into one, but do not perform the association
                reactions.append(
                    join_complexes_21(reactant1, (strand_num1, dom_num1),
                                      reactant2, (strand_num2, dom_num2)))

    output = []
    for complex, location1, location2 in reactions:
        # build "before" and "after" loop structures via find_on_loop ...
        out = find_on_loop(
            complex, location1, lambda (dom1, struct1, loc1),
            (dom2, struct2, loc2): loc1 == location1 and loc2 == location2)

        [(loc1s, before, loc2s, after)] = out

        # zipper for max-helix semantics
        if max_helix:
            loc1s, before, loc2s, after = zipper(complex, loc1s[0], before,
                                                 loc2s[0], after,
                                                 filter_bind11)

        [loc1s, before, loc2s, after] = map(Loop,
                                            [loc1s, before, loc2s, after])

        (product, rotations) = do_bind11(complex, loc1s.locs, loc2s.locs)

        try:
            reaction = PepperReaction(sorted([reactant1, reactant2]),
                                      [product], 'bind21')
        except DSDDuplicationError, e:
            #assert opening_rate(length) == PepperReaction.dictionary[e.solution].rate
            reaction = e.existing

        length = len(loc1s)
        reaction.rate = bimolecular_binding_rate(length)

        output.append(reaction)
    def test_condense_simple(self):
        complexes, reactions = read_pil("""
        # File generated by peppercorn-v0.5.0
        
        # Domain Specifications 
        length d1 = 15
        length t0 = 5
        
        # Resting-set Complexes 
        c1 = t0 d1 
        c2 = d1( + ) t0* 
        c4 = t0( d1( + ) ) 
        c5 = d1 
        
        # Transient Complexes 
        c3 = t0( d1 + d1( + ) ) 
        
        # Detailed Reactions 
        reaction [bind21         =      100 /M/s ] c1 + c2 -> c3
        reaction [open           =       50 /s   ] c3 -> c1 + c2
        reaction [branch-3way    =       50 /s   ] c3 -> c4 + c5
        """)

        # (rs1) c1                c4 (rs3)
        #         \              /
        #          <---> c3 ---->
        #         /              \
        # (rs2) c2                c5 (rs4)

        # RestingSet representation
        rs1 = PepperMacrostate([complexes['c1']], memorycheck=False)
        rs2 = PepperMacrostate([complexes['c2']], memorycheck=False)
        rs3 = PepperMacrostate([complexes['c4']], memorycheck=False)
        rs4 = PepperMacrostate([complexes['c5']], memorycheck=False)

        # Frozensets instead of RestingMacrostates
        fs1 = frozenset([complexes['c1']])
        fs2 = frozenset([complexes['c2']])
        fs3 = frozenset([complexes['c4']])
        fs4 = frozenset([complexes['c5']])

        cplx_to_state = {  # maps Complex to its RestingMacrostate
            complexes['c1']: rs1,
            complexes['c2']: rs2,
            complexes['c4']: rs3,
            complexes['c5']: rs4
        }

        cplx_to_fate = {  # maps Complex to its SetOfFates
            complexes['c1']: SetOfFates([[rs1]]),
            complexes['c2']: SetOfFates([[rs2]]),
            complexes['c3']: SetOfFates([[rs1, rs2], [rs3, rs4]]),
            complexes['c4']: SetOfFates([[rs3]]),
            complexes['c5']: SetOfFates([[rs4]])
        }

        cplx_to_set = {  # maps Complex to its frozenset
            complexes['c1']: fs1,
            complexes['c2']: fs2,
            complexes['c4']: fs3,
            complexes['c5']: fs4
        }

        set_to_fate = {  # maps frozenset to the RestingMacrostate
            fs1: rs1,
            fs2: rs2,
            fs3: rs3,
            fs4: rs4
        }

        cond_react = PepperReaction([rs1, rs2], [rs3, rs4],
                                    'condensed',
                                    memorycheck=False)
        cond_react.rate = 100 * (float(50) / (50 + 50))

        enum = Enumerator(complexes.values(), reactions)
        enum.dry_run()  # does not change the rates!

        enumRG = PepperCondensation(enum)
        enumRG.condense()

        self.assertEqual(sorted([rs1, rs2, rs3, rs4]),
                         sorted(enumRG.resting_sets))
        self.assertDictEqual(set_to_fate, enumRG.set_to_fate)
        self.assertDictEqual(cplx_to_fate, enumRG.cplx_to_fate)
        #self.assertDictEqual(cplx_to_set,  info['complexes_to_resting_set'])

        self.assertEqual([cond_react], enumRG.condensed_reactions)
        self.assertEqual(cond_react.rate, enumRG.condensed_reactions[0].rate)
        self.assertEqual(enumRG.condensed_reactions[0].rate, 50)
Exemplo n.º 3
0
    def condense(self):
        """
        Reaction condensation.
        """
        if self._condensed_reactions is not None:
            raise CondensationError('condensation called twice')
        else:
            self._condensed_reactions = set()

        enum = self.enumerator

        for scc in self.SCCs:
            self.compute_fates(scc)

        for reaction in enum.reactions:
            # NOTE: Fast reactions have been handled by the fates
            if self.is_fast(reaction):
                continue

            # Filter reactions with no products/reactants
            if len(reaction.reactants) == 0 and len(reaction.products) == 0:
                raise Exception('before we continue, see why that happens...')
                #continue

            # Get the corresponding fates (resting sets)
            reactant_fates = map(self.get_fates, reaction.reactants)
            product_fates = map(self.get_fates, reaction.products)

            # Get all combinations of reactant and product fates,
            new_reactant_combinations = cartesian_sum(reactant_fates)
            new_product_combinations = cartesian_sum(product_fates)

            # Make sure each reactant is a resting set:
            #   F(c) is a singleton for each reactant c
            for e, Fc in enumerate(reactant_fates):
                if not Fc.is_singleton():
                    logging.error("Cannot condense reaction {}: ".format(reaction) + \
                            "reactant {} has multiple fates: F({}) = {}".format(
                                str(reaction.reactants[e]), str(reaction.reactants[e]), str(Fc)))
                    raise CondensationError()

            # Take all combinations of reactants and products
            new_reactant_product_combinations = it.product(
                new_reactant_combinations, new_product_combinations)

            # Generate new reactions with each non-trivial combination
            for (reactants, products) in new_reactant_product_combinations:
                reactants = sorted(reactants)
                products = sorted(products)

                # skip the trivial case
                if reactants == products:
                    continue

                try:
                    reaction = PepperReaction(reactants,
                                              products,
                                              rtype='condensed')
                except DSDDuplicationError, e:
                    logging.debug('duplicating PepperReaction: {}'.format(
                        e.existing))
                    reaction = e.existing

                reaction.rate = self.get_condensed_rate(reaction)
                self._condensed_reactions.add(reaction)
def branch_4way(reactant, max_helix=False, remote=True):
    """
    Returns a list of complex sets that can be created through one iteration of
    a 4 way branch migration reaction (each set consists of the molecules that
    result from the iteration; more than one molecule may result because branch
    migration can liberate strands and complexes).
    """

    reactions = []
    structure = reactant.pair_table

    # We loop through all domains
    for (strand_index, strand) in enumerate(structure):
        for (domain_index, domain) in enumerate(strand):

            # Unbound domains can't participate in branch migration
            if (structure[strand_index][domain_index] is None):
                continue

            start_loc = (strand_index, domain_index)

            # searches only 5'->3' direction around the loop for a bound domain that
            # has the same sequence (and therefore can be displaced)
            #
            #   z  _~_  z* (displacing)
            #  ___/   \___>
            #
            # <___     ___
            #     \_ _/
            #   z*  ~   z
            #

            # build products
            results = find_on_loop(reactant, start_loc, filter_4way)

            for e, (invader, xlinker, target, ylinker) in enumerate(results):
                if max_helix:
                    invader, _, target, _ = zipper(reactant, invader[0], None,
                                                   target[0], None,
                                                   filter_4way)
                results[e] = map(Loop, [invader, xlinker, target, ylinker])

            for (displacing, before, displaced, after) in results:
                (products, rotations) = do_4way_migration(
                    reactant, displacing.locs, (structure[dl[0]][dl[1]]
                                                for dl in displacing.locs),
                    (structure[bl[0]][bl[1]] for bl in displaced.locs),
                    displaced.locs)

                try:
                    reaction = PepperReaction([reactant], products,
                                              'branch-4way')
                    reaction.meta = (displacing, displaced, before, after)
                    reaction.rotations = rotations
                except DSDDuplicationError, e:
                    reaction = e.existing

                # skip remote toehold reactions
                if not remote:
                    # NOTE: both sides need to be remote!
                    if not ((not after.is_open and after.stems == 1
                             and after.bases == 0) or
                            (not before.is_open and before.stems == 1
                             and before.bases == 0)):
                        continue

                # calculate reaction constant
                reaction.rate = branch_4way_remote_rate(
                    len(displacing), before, after)

                reactions.append(reaction)
def branch_3way(reactant, max_helix=True, remote=True):
    """
    Returns a list of reaction pathways that can be created through one
    iteration of a 3 way branch migration reaction (more than one molecule may
    be produced by a reaction because branch migration can liberate strands and
    complexes).
    """

    reactions = []

    reactions = []
    structure = reactant.pair_table

    # We iterate through all the domains
    for (strand_index, strand) in enumerate(structure):
        for (domain_index, domain) in enumerate(strand):

            # The displacing domain must be free
            if (structure[strand_index][domain_index] is not None):
                continue

            # search 5'->3' and 3'->5' directions around the loop for a bound
            # domain that is complementary (and therefore can be displaced)

            start_loc = (strand_index, domain_index)

            # build products
            fwresults = find_on_loop(reactant,
                                     start_loc,
                                     filter_3way,
                                     direction=1)
            bwresults = find_on_loop(reactant,
                                     start_loc,
                                     filter_3way,
                                     direction=-1)

            results = []
            for (invader, xlinker, target, ylinker) in fwresults:
                if max_helix:
                    invader, xlinker, target, ylinker = zipper(
                        reactant, invader[0], xlinker, target[0], ylinker,
                        filter_3way)
                ylinker += invader
                results.append(
                    map(Loop, [invader[::-1], xlinker, target[::-1], ylinker]))

            for (invader, xlinker, target, ylinker) in bwresults:
                if max_helix:
                    invader, xlinker, target, ylinker = zipper(
                        reactant, invader[0], xlinker, target[0], ylinker,
                        filter_3way)
                ylinker += invader[::-1]
                results.append(map(Loop, [invader, xlinker, target, ylinker]))

            for (displacing, before, bound, after) in results:
                (products,
                 rotations) = do_3way_migration(reactant,
                                                list(displacing.locs),
                                                list(bound.locs))

                try:
                    reaction = PepperReaction([reactant], products,
                                              'branch-3way')
                    reaction.meta = (displacing, bound, before, after)
                    reaction.rotations = rotations
                except DSDDuplicationError, e:
                    reaction = e.existing

                # skip remote toehold reactions if directed
                if not remote:
                    if not (not before.is_open and before.stems == 1
                            and before.bases == 0):
                        # print "Rejecting... " + reaction.kernel_string
                        # import pdb; pdb.set_trace()
                        continue

                # calculate reaction constant
                reaction.rate = branch_3way_remote_rate(
                    len(displacing), before, after)

                reactions.append(reaction)
def bind11(reactant, max_helix=True, remote=None):
    """
    Returns a list of reaction pathways which can be produced by 1-1 binding
    reactions of the argument complex. The 1-1 binding reaction is the
    hybridization of two complementary unpaired domains within a single complex
    to produce a single unpseudoknotted product complex.

    Note: Remote is ineffective, but may be set for convencience
    """

    reactions = []
    structure = reactant.pair_table

    # We iterate through all the domains
    for (strand_index, strand) in enumerate(structure):
        for (domain_index, domain) in enumerate(strand):

            # The displacing domain must be free
            if structure[strand_index][domain_index] is not None:
                continue

            start_loc = (strand_index, domain_index)

            # search both directions around the loop for a bound domain that
            # has the same sequence (and therefore can be displaced)
            results = find_on_loop(reactant, start_loc, filter_bind11)

            if results:
                assert len(results) == \
                        len(find_on_loop(reactant, start_loc, filter_bind11, direction=-1))

            for e, (invader, xlinker, target, ylinker) in enumerate(results):
                if max_helix:
                    invader, xlinker, target, ylinker = zipper(
                        reactant, invader[0], xlinker, target[0], ylinker,
                        filter_bind11)
                results[e] = map(Loop, [invader, xlinker, target, ylinker])

            # build products
            for (loc1s, before, loc2s, after) in results:

                try:
                    (product, rotations) = do_bind11(reactant, loc1s.locs,
                                                     loc2s.locs)
                except AssertionError:
                    continue

                try:
                    reaction = PepperReaction([reactant], [product], 'bind11')
                    reaction.meta = (loc1s, loc2s, before, after)
                    reaction.rotations = rotations
                except DSDDuplicationError, e:
                    reaction = e.existing

                # length of invading domain
                length = len(loc1s)

                # calculate reaction constant
                reaction.rate = binding_rate(length, before, after)

                reactions.append(reaction)
    output = []
    for product_set, rotations, length, meta in reactions:
        try:
            reaction = PepperReaction([reactant], sorted(product_set), 'open')
            reaction.rotations = rotations
            reaction.meta = meta
        except DSDDuplicationError, e:
            reaction = e.existing

        # discard reactions where the release cutoff is greater than the threshold
        if len(reaction.products) == 1 and length > release_11:
            continue
        elif len(reaction.products) > 1 and length > release_1N:
            continue

        reaction.rate = opening_rate(length)
        output.append(reaction)

    return sorted(list(set(output)))


def do_single_open(reactant, loc):
    struct = reactant.pair_table
    loc1 = loc
    loc2 = struct[loc1[0]][loc1[1]]
    assert struct[loc2[0]][loc2[1]] == loc1
    struct[loc1[0]][loc1[1]] = None
    struct[loc2[0]][loc2[1]] = None
    newstr = pair_table_to_dot_bracket(struct)
    try:
        product = PepperComplex(reactant.sequence, newstr)