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 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)
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)
                        rotations = 0
                    except DSDDuplicationError, e:
                        release_reactant = e.existing
                        rotations = e.rotations

                    product_set = release_reactant.split()
                    meta = None
                    reactions.append(
                        (product_set, rotations, helix_length, meta))

    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)))