Обратите внимание, что это часть заметки, взятой из этого курса Coursera по алгоритму ДНК.

Проблема: при сборке генома нам дается набор прочтений, которые являются подстроками исходной последовательности, и наша цель — воссоздать исходную последовательность. Например. задано ATCG, TCGC, GCAA => ATCGCGCAA

Один из способов сделать это — предположить, что у нас есть все до единого k-меры (чтения длины k), взятые из чтения. Например. взятие всех 4-меров ATCGCGCAA дает ATCG, TCGC, CGCG, GCGC, CGCA, GCAA. Конечно, в реальном мире нет гарантии, что предположение верно, но это хорошая отправная точка.

График Де Брёйна – удобный способ реконструкции исходной последовательности. Чтобы построить такой граф, мы должны выполнить следующие шаги:

Шаг 1: взять все (k-1)-меры из набора k-меров, например ATCG, TCGC -> ATC, TCG, TCG, CGC. У нас должен быть удвоенный размер чтения k-mer.

Шаг 2: построить мультиграф с узлами, являющимися k-1-мерами; провести ребро между двумя мерами k-1 только в том случае, если два меров k-1 взяты из одного и того же чтения. Например. ATCG, TCGC дает

Шаг 3: граф, построенный таким образом, гарантированно имеет эйлеров след, мы идем по следу и соединяем узлы, чтобы сформировать нашу исходную последовательность.

Подробнее: почему алгоритм работает? Второй k-1-мер каждого k-мера является началом k-1-мера в следующем k-мере, продолжаясь таким образом до конца последовательности.

Когда алгоритм не сработает? когда у нас есть повторы в последовательности. Например, ниже приведен график, полученный путем взятия всех 3-меров ATGATATG.

В этом случае мы могли бы построить TGATGATG из графа, выполнив эйлеров цикл (точнее, контур), который отличается от исходной последовательности. Чтобы преодолеть двусмысленность из-за коротких повторов, мы можем увеличить длину чтения, чтобы у нас было больше различных k-1-меров.

На пути к реальному алгоритму сборки

Мы видели алгоритм для сборки чтений k-mer. Мы можем ослабить условие, чтобы принимать чтения выше заданной длины, и разбить каждый k-мер на (k-n)-мер, чтобы учитывать чтения, которые имеют k-n перекрытий вместо k-1 перекрытия.

Кроме того, мы можем попытаться реконструировать части, которые легче собрать (контиги), и исключить неоднозначные части. Например, ATGGCCGAAGCAACGGGTTTGTGACGCGCGCGCGCGACCAGG имеет длинные повторы CG в середине, но два конца довольно легко собрать.

Фрагмент кода здесь:

# coding: utf-8
# In[2]:
def debruijnize(reads):
    nodes = set()
    not_starts = set()
    edges = []
    for r in reads:
        r1 = r[:-1]
        r2 = r[1:]
        nodes.add(r1)
        nodes.add(r2)
        edges.append((r1,r2))
        not_starts.add(r2)
    return (nodes,edges,list(nodes-not_starts))
# In[3]:
def build_k_mer(str,k):
    return [str[i:k+i] for i in range(0,len(str)-k+1)]
# In[4]:
reads = build_k_mer("ATCGTTGCGCGACCG",4)
print(reads)
# In[5]:
G = debruijnize(reads)
print(G)
# In[6]:
def make_node_edge_map(edges):
    node_edge_map = {}
    for e in edges:
        n = e[0]
        if n in node_edge_map:
            node_edge_map[n].append(e[1])
        else:
            node_edge_map[n] = [e[1]]
    return node_edge_map
m = make_node_edge_map(G[1])
print(m)
# In[7]:
def eulerian_trail(m,v):
    nemap = m
    result_trail = []
    start = v
    result_trail.append(start)
    while(True):
        trail = []
        previous = start
        while(True):
            
            if(previous not in nemap):
                break
            next = nemap[previous].pop()
            if(len(nemap[previous]) == 0):
                nemap.pop(previous,None)
            trail.append(next)
            if(next == start):
                break;
            previous = next
        # completed one trail
        print(trail)
        index = result_trail.index(start)
        result_trail = result_trail[0:index+1] + trail + result_trail[index+1:len(result_trail)]
        # choose new start
        if(len(nemap)==0):
          break
        found_new_start = False
        for n in result_trail:
            if n in nemap:
                start = n
                found_new_start = True
                break # from for loop
        if not found_new_start:
            print("error")
            print("result_trail",result_trail)
            print(nemap)
            break
    return result_trail
start = G[2][0] if (len(G[2]) > 0) else G[0][0]
print m
t = eulerian_trail(m,start)
print(t)
# In[8]:
def visualize_debruijn(G):
    nodes = G[0]
    edges = G[1]
    dot_str= 'digraph "DeBruijn graph" {\n '
    for node in nodes:
        dot_str += '    %s [label="%s"] ;\n' %(node,node)
    for src,dst in edges:
        dot_str += '    %s->%s;\n' %(src,dst)
    return dot_str + '}\n'
# In[9]:
get_ipython().magic(u'load_ext gvmagic')
# In[10]:
get_ipython().magic(u'dotstr visualize_debruijn(G)')
# In[11]:
def assemble_trail(trail):
    if len(trail) == 0:
        return ""
    result = trail[0][:-1]
    for node in trail:
        result += node[-1]
    return result
assemble_trail(t)
# In[12]:
def test_assembly_debruijn(t,k):
    reads = build_k_mer(t,k)
    G = debruijnize(reads)
    v = visualize_debruijn(G)
    nemap = make_node_edge_map(G[1])
    print(G)
    print(v)
    start = next(iter(G[2])) if (len(G[2]) > 0) else next(iter(G[0]))
    trail = eulerian_trail(nemap,start)
    return assemble_trail(trail)
# In[17]:
test_assembly_debruijn("ATGGCCGAAGCAACGGGTTTGTGACGCGCGCGCGCGACCAGG",4)