Professional Documents
Culture Documents
Cas 3
Cas 3
Cas 3
Kratke sekvence dobijene iz sekvencera (eng reads) dele se na kraće, preklapajuće sekvence jednakih
dužina, k-mere (eng k-mers), gde k predstavlja dužinu kratke sekvence.
Primer:
sekvenca: ATCGTG
k: 3
k-meri (3-meri):
ATC
TCG
CGT
GTG
Sledeći korak je konstrukcija grafa. Jedna opcija je konstrukcija grafa čiji su čvorovi k-meri dok grane
povezuju preklapajuće k-mere. Rekonstrukcija polazne sekvence ogledala bi se u pronalaženju puta koji
prolazi kroz sve čvorove grafa tačno jednom (Hamiltonov put), što je NP težak problem. Druga opcija je
konstrukcija grafa čije su grane k-meri spajajući čvorove koji predstavljaju prefiks i sufiks grane. Ovim
postupkom se problem svodi na pronalaženje puta koji prolazi preko svake grane tačno jednom (Ojlerov put
ili Ojlerov ciklus) što je problem polinomijalne složenosti. Rezultujući graf naziva se De Bruijnov graf.
Za početak, potrebno je pripremiti odgovarajuću klasu Graph koja će čuvati graf, definisan listom
susedstva, i pomoćne funkcije koje će biti korišćene za pronalaženje puteva
In [1]:
adjecency_list = {
'A': ['B','C'],
'B': ['A'],
'C': []
Zatim se strukturi Graph dodaje funkcija koja pronalazi Ojlerov ciklus. Da bi u grafu postojao Ojlerov ciklus,
polazeći od bilo kog čvora, čvorovi čiji ulazni i izlazni stepeni su različiti se povezuju dodatnom granom kako
bi razlika u stepenima bila uklonjena.
In [2]:
import copy
import random
class Graph:
self.adjacency_list = adjacency_list
# Metod vraća listu susednih čvorova zadatog čvora v, uključujući potencijalne dupl
ikate
return self.adjacency_list[v]
self.adjacency_list[u].append(v)
# Metod računa izlazni stepen čvora v (broj grana koje izlaze iz čvora v)
return len(self.adjacency_list[v])
# Metod računa ulazni stepen čvora v (broj grana koje ulaze u čvor v)
degree = 0
for u in neighbors:
if u == v:
degree += 1
return degree
edges = []
for w in neighbors:
if w == v:
edges.append((u,v))
return edges
# Metod vraća sve izlazne grane iz čvora v ka susedima w u obliku uređenog para (v,
w)
# Metod vraća sve grane koje postoje u grafu u obliku uređenih parova (u, v)
def all_edges(self):
edges = []
for w in self.adjacency_list:
edges += self.outbound_edges(w)
return edges
# Metod vraća listu čvorova čiji se ulazni stepen razlikuje od izlaznog stepena
def get_non_balanced(self):
non_balanced = []
for w in self.adjacency_list:
in_deg = self.in_degree(w)
out_deg = self.out_degree(w)
if in_deg != out_deg:
non_balanced.append(w)
return non_balanced
# Metod pronalazi dva nebalansirana čvora i povezuje ih granom tako da više ni jeda
n
def close_to_eulerian(self):
non_balanced = self.get_non_balanced()
if len(non_balanced) != 2:
return None
[u, v] = non_balanced
self.adjacency_list[u].append(v)
else:
self.adjacency_list[v].append(u)
def is_connected(self):
for v in self.adjacency_list:
visited = set([])
stack = [v]
v = stack[-1]
visited.add(v)
has_neighbor = False
for w in self.get_neighbors(v):
if w not in visited:
stack.append(w)
has_neighbor = True
break
if not has_neighbor:
stack.pop()
if len(visited) != len(self.adjacency_list.keys()):
return False
return True
# većim od 1
def is_simple(self):
for v in self.adjacency_list:
if self.in_degree(v) > 1:
return False
return True
# veći od 1
def get_non_simple_node(self):
for v in self.adjacency_list:
if self.in_degree(v) > 1:
return v
return None
x = f'{v}-{random.random()}'
self.adjacency_list[x] = []
self.adjacency_list[u].remove(v)
self.adjacency_list[v].remove(w)
self.add_neighbor(u, x)
self.add_neighbor(x, w)
# jednake jedinici
def eulerian_cycle(self):
current_node = list(self.adjacency_list.keys())[0]
unexplored_edges = self.all_edges()
cycle = [current_node]
selected_edge = unexplored_from_current[0]
(u, v) = selected_edge
unexplored_edges.remove(selected_edge)
current_node = v
cycle.append(v)
for i in range(len(cycle)):
v = cycle[i]
if self.has_unexplored_edge(v, unexplored_edges):
current_node = v
return cycle
def all_eulerian_cycles(self):
all_graphs = [self]
while True:
found = False
non_simple = None
for g in all_graphs:
if not g.is_simple():
found = True
non_simple = g
break
if not found:
break
v = non_simple.get_non_simple_node()
inbound = non_simple.inbound_edges(v)
outbound = non_simple.outbound_edges(v)
if u == v and v == w:
continue
new_graph = Graph(copy.deepcopy(non_simple.adjacency_list))
new_graph.bypass(u, v, w)
if new_graph.is_connected():
all_graphs.append(new_graph)
all_graphs.remove(non_simple)
unique_cycles = []
unique_cycles.append(cycle)
return unique_cycles
def maximal_non_branching_paths(self):
paths = []
visited = set([])
for v in self.adjacency_list:
if not self.is_1_in_1_out(v):
if self.out_degree(v) > 0:
non_branching_path = [v, w]
while self.is_1_in_1_out(w):
visited.add(w)
(w, u) = self.outbound_edges(w)[0]
non_branching_path.append(u)
w = u
paths.append(non_branching_path)
# prethodnim obilaskom
for v in self.adjacency_list:
visited.add(v)
non_branching_path = [v]
while True:
outbound = self.outbound_edges(v)
if len(outbound) == 0:
break
visited.add(w)
non_branching_path.append(w)
outbound = self.outbound_edges(w)
if len(outbound) > 0:
w = u
else:
break
break
return paths
In [3]:
g = Graph({
'A': ['B'],
'C': ['D'],
'F': ['D','E'],
'G': ['F']
})
g.close_to_eulerian()
g.eulerian_cycle()
Out[3]:
['E', 'G', 'F', 'D', 'A', 'B', 'C', 'D', 'B', 'E', 'F', 'E']
Konačno, od sekvence (ili sekvenci) je potrebno konstruisati De Bruijnov graf koji će naslediti sve osobine
grafa ali će listu susedstva konstruisati pomoću k-mera.
In [4]:
class DeBruijn(Graph):
kmers = []
adjacency_list = {}
n = len(read)
kmer = read[i: i + k]
# u => v
# kmer = ATG
# u = AT, v = TG
u = kmer[:-1]
v = kmer[1:]
if u not in adjacency_list:
adjacency_list[u] = [v]
else:
adjacency_list[u].append(v)
if v not in adjacency_list:
adjacency_list[v] = []
self.adjacency_list = adjacency_list
In [5]:
reads = ['TAATGCCATGGGATGTT']
k = 3
dg = DeBruijn(k, reads)
In [6]:
dg.adjacency_list
Out[6]:
{'TA': ['AA'],
'AA': ['AT'],
'GC': ['CC'],
'CC': ['CA'],
'CA': ['AT'],
'GA': ['AT'],
'GT': ['TT'],
'TT': []}
In [7]:
dg.close_to_eulerian()
cycle = dg.eulerian_cycle()
cycle
Out[7]:
['TA',
'AA',
'AT',
'TG',
'GC',
'CC',
'CA',
'AT',
'TG',
'GG',
'GG',
'GA',
'AT',
'TG',
'GT',
'TT',
'TA']
In [8]:
seq = cycle[0]
# Prolazi se kroz sve čvorove u ciklusu osim poslednjeg, koji zatvara ciklus
seq += cycle[i][-1]
seq
Out[8]:
'TAATGCCATGGGATGTT'
In [9]:
dg.all_eulerian_cycles()
Out[9]:
[['TA',
'AA',
'AT',
'TG',
'GG',
'GG',
'GA',
'AT',
'TG',
'GC',
'CC',
'CA',
'AT',
'TG',
'GT',
'TT',
'TA'],
['TA',
'AA',
'AT',
'TG',
'GC',
'CC',
'CA',
'AT',
'TG',
'GG',
'GG',
'GA',
'AT',
'TG',
'GT',
'TT',
'TA']]
In [12]:
dg = DeBruijn(k, reads)
dg.maximal_non_branching_paths()
Out[12]:
['AT', 'TG'],
['AT', 'TG'],
['AT', 'TG'],
['TG', 'GG'],
['GG', 'GG'],