def _bowtie_tangle():
        top = linear_path()
        decision_segment = top[middle_pivot:middle_pivot + ksize + 2]
        decision_segment = mutate_position(decision_segment, 0)
        decision_segment = mutate_position(decision_segment, -1)

        bottom = random_sequence(exclude=decision_segment)[:middle_pivot] \
                 + decision_segment
        bottom += random_sequence(exclude=bottom)[:length - middle_pivot -
                                                  len(decision_segment)]

        consume_collector(top, bottom)
        check_fp_collector((lambda G: count_decision_nodes(top, G, ksize), {
            (2, 2): 1
        }), (lambda G: count_decision_nodes(bottom, G, ksize), {
            (2, 2): 1
        }))

        return (top, bottom), middle_pivot
    def _suffix_circular_tangle():
        base = suffix_circular()

        L = middle_pivot
        decision_segment = base[L:L + ksize + 1]
        decision_segment = mutate_position(decision_segment, 0)
        decision_segment = mutate_position(decision_segment, -1)
        inducer = random_sequence(exclude=decision_segment)[:L] \
                 + decision_segment
        inducer += random_sequence(exclude=inducer)[:length - L -
                                                    len(decision_segment)]

        consume_collector(base, inducer)
        check_fp_collector((lambda G: count_decision_nodes(base, G, ksize), {
            (1, 2): 1,
            (2, 1): 1
        }), (lambda G: count_decision_nodes(inducer, G, ksize), {
            (1, 2): 1,
            (2, 1): 1
        }))

        return (base, inducer), L
    def _hourglass_tangle():
        top = linear_path()
        L = middle_pivot
        decision_segment = top[L:L + ksize + 1]
        decision_segment = mutate_position(decision_segment, 0)
        decision_segment = mutate_position(decision_segment, -1)

        bottom = random_sequence(exclude=decision_segment)[:L] \
                 + decision_segment
        bottom += random_sequence(exclude=bottom)[:length - L -
                                                  len(decision_segment)]

        consume_collector(top, bottom)
        check_fp_collector((lambda G: count_decision_nodes(top, G, ksize), {
            (1, 2): 1,
            (2, 1): 1
        }), (lambda G: count_decision_nodes(bottom, G, ksize), {
            (1, 2): 1,
            (2, 1): 1
        }))

        return (top, bottom), L
    def _right_tip():
        sequence = random_sequence()
        pivot = internal_pivot

        if pivot < 1:
            raise ValueError("ksize too large for length")

        # the branch kmer
        tip = mutate_position(sequence[pivot + 1:pivot + 1 + ksize], -1)

        consume_collector(sequence, tip)
        check_fp_collector(
            (lambda G: count_decision_nodes(sequence, G, ksize), {
                (1, 2): 1
            }), (lambda G: count_decision_nodes(tip, G, ksize), {}))

        return (sequence, tip), pivot
    def _left_hairpin():
        core = linear_path()
        pos = len(core) // 2
        if core[pos - 1] == core[-1]:
            core = mutate_position(core, -1)
        hdn = core[pos:pos + ksize]
        result = core + hdn

        _collector = consume_collector()
        _collector.pop()
        consume_collector(result)
        _check_fp_collector = check_fp_collector()
        _check_fp_collector.pop()
        check_fp_collector((lambda G: count_decision_nodes(result, G, ksize), {
            (2, 1): 2
        }))

        return result, pos
    def _snp_bubble():
        wildtype_sequence = linear_path()
        decision_L = middle_pivot
        decision_R = decision_L + ksize + 1

        if decision_L < 1:
            raise ValueError("ksize too long for length")

        snp_sequence = mutate_position(wildtype_sequence, decision_L + ksize)

        consume_collector(wildtype_sequence, snp_sequence)
        check_fp_collector(
            (lambda G: count_decision_nodes(wildtype_sequence, G, ksize), {
                (1, 2): 1,
                (2, 1): 1
            }), (lambda G: count_decision_nodes(snp_sequence, G, ksize), {
                (1, 2): 1,
                (2, 1): 1
            }))

        return (wildtype_sequence, snp_sequence), decision_L, decision_R
    def _circular_key():
        loop = linear_path()
        loop = loop + loop[:ksize - 1]
        if pivot in (length - ksize, length - ksize - 1):
            _pivot = pivot + ksize - 1
        else:
            _pivot = pivot

        loop_kmers = list(kmers(loop, ksize))
        tail_kmers = [
            loop_kmers[i % len(loop_kmers)]
            for i in range(_pivot + 1, _pivot + 1 + ksize)
        ]
        tail = ''.join((kmer[0] for kmer in tail_kmers))
        tail = mutate_position(tail, -1)

        consume_collector(loop, tail)
        check_fp_collector((lambda G: count_decision_nodes(loop, G, ksize), {
            (1, 2): 1
        }))

        return (loop, tail), _pivot
def test_mutate_position():
    assert mutate_position('AAAA', 2) in ['AACA', 'AAGA']
    assert mutate_position('TTTT', 2) in ['TTCT', 'TTGT']
    assert mutate_position('CCCC', 2) in ['CCAC', 'CCTC']
    assert mutate_position('GGGG', 2) in ['GGAG', 'GGTG']