---
# System prepended metadata

title: TAP v2

---

# TAP v2

## Random notes
the problem with anchor graphs has been to try and make them too complicated.

take unique 63-mers from the distribution.

take perfect matches from the long reads.

discard anything that is not unique (i.e. double positions) -> TAP.

If short reads are available, distances with the short reads can be used too. Once a local subset is ordered (with the short reads), they can be used to refer to a central point. (i.e. a hash of kmers that refers to a has of central kmers plus short offsets). Once the central kmers are ordered, the offsets can be used to fill the non-central k-mers.

Because the anchor points are unique k-mers they can be single positions (offsets) in the read. So, when tapping, the original distance in the read is recovered.

To make it easier to work with real life long reads, all of this can be done in homopolimer-compressed space.


The problem has been always that we focused on the wrong conditions. Fixing copy-number 1 on contigs was idiotic. We can fix copy number 1 on k-mers, which we can guess reasonably well, and then order gives position. The Sequence Distance Graph is only an intermediate concept to a unique-k-mer distance graph.

Pan-genome versions of this could use the unique k-mers in different haplotypes/colors to find disorder in the set to call variants.

Integration with markers is as easy as maping a marker to a position.


## Example run

Try with the octoploid from pseudoseq.



```python
# rough TAP v2 implementation
%cd /Users/clavijob/pseudoseq_paper/new
import SDGpython as SDG
ws=SDG.WorkSpace()
peds=ws.add_paired_reads_datastore('octo_pe.prseq')
lords=ws.add_long_reads_datastore('octo_long.loseq')

# new function: kc.kmer_unlinked_graph(count_name,min_freq,max_freq) -> sdg with kmers as nodes.
SDG.count_kmers_as_graph_nodes(ws.sdg,peds,31,50,100,1)
print(len(ws.sdg.get_all_nodeviews()))
kc=ws.add_kmer_counter("main")
kc.add_count("pe",peds)
kc.set_kci_peak(75)
```
```txt
/Users/clavijob/pseudoseq_paper/new
248880
```
```python
print(ws.sdg.stats_by_kci())
s=SDG.Strider(ws)
s.add_datastore(peds)
peds.mapper.path_reads(31)
#collapse nodes into unique-31-mer unitigs --> warning: could connects across collapsed 30-mers if strider fails.
print("starting... %s" %datetime.now())
total_nodes=len(ws.sdg.get_all_nodeviews())
for nv in ws.sdg.get_all_nodeviews():
    if nv.node_id()%100000 == 0: print("\rnodos: %s/%s" %(nv.node_id(), total_nodes), end='')
    fw=s.stride_single_strict(nv.node_id()).nodes
    bw=s.stride_single_strict(-nv.node_id()).nodes
    for fw in [fw,bw]:
        if len(fw)>1:
            if ws.sdg.get_nodeview(fw[0]).sequence()[1:]==ws.sdg.get_nodeview(fw[1]).sequence()[:30]:
                if not ws.sdg.are_connected(-fw[0],fw[1]):
                    ws.sdg.add_link(-fw[0],fw[1],-30)
print("Done for loop... %s" %datetime.now())                    
ws.sdg.join_all_unitigs()
print("Done for Join all unitigs... %s" %datetime.now())                    
kc.update_graph_counts()
print(ws.sdg.stats_by_kci())
```
```txt
 -----------------------------------------------------------------------------------
|  KCI  |    Total bp   |   Nodes  |  Tips   |     N25    |     N50    |     N75    |
|-------+---------------+----------+---------+------------+------------+------------|
| None  |       7715280 |   248880 |  248880 |         31 |         31 |         31 |
| < 0.5 |             0 |        0 |       0 |          0 |          0 |          0 |
| ~ 1   |             0 |        0 |       0 |          0 |          0 |          0 |
| ~ 2   |             0 |        0 |       0 |          0 |          0 |          0 |
| ~ 3   |             0 |        0 |       0 |          0 |          0 |          0 |
| > 3.5 |             0 |        0 |       0 |          0 |          0 |          0 |
|-------+---------------+----------+---------+------------+------------+------------|
| All   |       7715280 |   248880 |  248880 |         31 |         31 |         31 |
 -----------------------------------------------------------------------------------

 -----------------------------------------------------------------------------------
|  KCI  |    Total bp   |   Nodes  |  Tips   |     N25    |     N50    |     N75    |
|-------+---------------+----------+---------+------------+------------+------------|
| None  |         37288 |     1058 |    1058 |         38 |         36 |         33 |
| < 0.5 |             0 |        0 |       0 |          0 |          0 |          0 |
| ~ 1   |        478502 |     7839 |    7839 |         61 |         61 |         61 |
| ~ 2   |             0 |        0 |       0 |          0 |          0 |          0 |
| ~ 3   |             0 |        0 |       0 |          0 |          0 |          0 |
| > 3.5 |             0 |        0 |       0 |          0 |          0 |          0 |
|-------+---------------+----------+---------+------------+------------+------------|
| All   |        515790 |     8897 |    8897 |         61 |         61 |         61 |
 -----------------------------------------------------------------------------------
 ```
 ```python
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,31)
lrr.map(31)
lrr.thread_reads(end_matches=1)
tdg=lrr.dg_from_threads()
```
![](https://i.imgur.com/mmc9em4.png)

```python
u=SDG.LinkageUntangler(tdg)
u.select_all()
print(len([1 for x in u.selected_nodes if x==True]),"nodes selected to start with")
conflicted_ends=set()
for x in range(2):
    nsl=u.make_nextselected_linkage(7)
    nsl.remove_transitive_links()
    for fnv in nsl.get_all_nodeviews():
        for nv in [fnv,fnv.rc()]:
            if len(nv.next())>1:
                conflicted_ends.add(-nv.node_id())
                for lfw in nv.next():
                    conflicted_ends.add(lfw.node().node_id())
    for nid in conflicted_ends:
        if nid>0 and -nid in conflicted_ends:
            u.selected_nodes[nid]=False
    print(len([1 for x in u.selected_nodes if x==True]),"nodes selected after cleaning up multi-connected")


    
print('leftover nodes with multiple connections: seq'+',seq'.join([str(abs(nv.node_id())) for nv in nsl.get_all_nodeviews() if len(nv.next())>1 or len(nv.prev())>1]))

print('leftover nodes with multiple connections: '+len([str(abs(nv.node_id())) for nv in nsl.get_all_nodeviews() if len(nv.next())>1 or len(nv.prev())>1]))

nsl.write_to_gfa1('nsl.gfa',selected_nodes=[x[0] for x in enumerate(u.selected_nodes) if x[1]])
```

```txt
257630 nodes selected to start with
257620 nodes selected after cleaning up multi-connected
257619 nodes selected after cleaning up multi-connected
leftover nodes with multiple connections: seq60264,seq185450,seq196603,seq250395,seq250558,seq251849,seq253004,seq253714,seq254169,seq254564,seq254895,seq256825,seq256963,seq257437,seq257602
```

![](https://i.imgur.com/9LbMypa.png)


## Trying with the PB HiFi dmel / strawberry datasets

1) construct graph at k=63

2) pop homopolymer bubbles

```python
def homocomp(s):
    r=s[0]
    for c in s:
        if r[-1]!=c: r+=c
    return r
    
bb=[1]
while bb:
    bb=[]
    for nv in ws.sdg.get_all_nodeviews():
        if is_bubble_side(nv):
            pv=nv.parallels()[0]
            if nv.size()>pv.size() and homocomp(nv.sequence())==homocomp(pv.sequence()):
                bb.append(pv.node_id())
    for x in bb:
        ws.sdg.remove_node(x)
    ws.sdg.join_all_unitigs()
    print(len(bb))
```

3) Expand canonical repeats

```python
ge=SDG.GraphEditor(ws)
solved=unsolved=0
for nv in ws.sdg.get_all_nodeviews():
    if is_canonical_repeat(nv):
        nidp0=nv.prev()[0].node().node_id()
        nidp1=nv.prev()[1].node().node_id()
        nidn0=nv.next()[0].node().node_id()
        nidn1=nv.next()[1].node().node_id()
        pA=set([abs(x) for x in lrr19.node_reads[abs(nidp0)]])
        pB=set([abs(x) for x in lrr19.node_reads[abs(nidp1)]])
        pC=set([abs(x) for x in lrr19.node_reads[abs(nidn0)]])
        pD=set([abs(x) for x in lrr19.node_reads[abs(nidn1)]])
        pAB=pA.intersection(pB)
        pCD=pC.intersection(pD)
        pA=pA-pAB
        pB=pB-pAB
        pC=pC-pCD
        pD=pD-pCD
        pAC=len(pA.intersection(pC))
        pAD=len(pA.intersection(pD))
        pBC=len(pB.intersection(pC))
        pBD=len(pB.intersection(pD))
        if pAC<5 and pAD>20 and pBC>20 and pBD<5:
            solved+=1
            ge.queue_node_expansion(nv.node_id(),[[-nidp0,nidn1],[-nidp1,nidn0]])
        elif pAD<5 and pAC>20 and pBD>20 and pBC<5:
            solved+=1
            ge.queue_node_expansion(nv.node_id(),[[-nidp0,nidn0],[-nidp1,nidn1]])
        else: unsolved+=1
```

4) Solve "double joins"

```python
def is_double_join(nv):
    if len(nv.next())!=2: return False
    if len(nv.next()[0].node().prev())!=2 or len(nv.next()[1].node().prev())!=2: return False
    n0ps=set([x.node().node_id() for x in nv.next()[0].node().prev()])
    n1ps=set([x.node().node_id() for x in nv.next()[1].node().prev()])
    if n0ps!=n1ps: return False
    n0ps=set([x.node().node_id() for x in nv.next()[0].node().prev()[0].node().next()])
    n1ps=set([x.node().node_id() for x in nv.next()[0].node().prev()[1].node().next()])
    if n0ps!=n1ps: return False
    return True
solved=unsolved=0    
for fnv in ws.sdg.get_all_nodeviews():
    for nv in [fnv,fnv.rc()]:
        if is_double_join(nv):
            nidn0=nv.next()[0].node().node_id()
            nidn1=nv.next()[1].node().node_id()
            nidp0=nv.next()[0].node().prev()[0].node().node_id()
            nidp1=nv.next()[0].node().prev()[1].node().node_id()
            
            pA=set([abs(x) for x in lrr19.node_reads[abs(nidp0)]])
            pB=set([abs(x) for x in lrr19.node_reads[abs(nidp1)]])
            pC=set([abs(x) for x in lrr19.node_reads[abs(nidn0)]])
            pD=set([abs(x) for x in lrr19.node_reads[abs(nidn1)]])
            pAB=pA.intersection(pB)
            pCD=pC.intersection(pD)
            pA=pA-pAB
            pB=pB-pAB
            pC=pC-pCD
            pD=pD-pCD
            pAC=len(pA.intersection(pC))
            pAD=len(pA.intersection(pD))
            pBC=len(pB.intersection(pC))
            pBD=len(pB.intersection(pD))
            if pAC<5 and pAD>20 and pBC>20 and pBD<5:
                solved+=1
                ws.sdg.remove_link(-nidp0,nidn0)
                ws.sdg.remove_link(-nidp1,nidn1)
            elif pAD<5 and pAC>20 and pBD>20 and pBC<5:
                solved+=1
                ws.sdg.remove_link(-nidp0,nidn1)
                ws.sdg.remove_link(-nidp1,nidn0)
            else: unsolved+=1
            
```

5) Strider links?
```python
anchor_graph=SDG.DistanceGraph(ws.sdg)
anchor_graph_multi=SDG.DistanceGraph(ws.sdg)
for x in range(len(s.is_anchor)):
  if s.is_anchor[x]:
    for l in s.links_bw[x] + s.links_fw[x]:
      if not anchor_graph_multi.are_connected(l.source,l.dest): anchor_graph_multi.add_link(l.source, l.dest, l.dist)
      if s.is_anchor[abs(l.dest)]:
        if not anchor_graph.are_connected(l.source,l.dest): anchor_graph.add_link(l.source, l.dest, l.dist)
anchor_graph.remove_transitive_links()
```


```python
def get_node_inmediate_neighbours(node):
    """ Uno nodo para adelante y un nodo para atras """
    group=[]
    for x in Counter([-x for path in peds.mapper.all_paths_fw(-node) for x in path]).most_common(3):
        if x[0]!=0 and x[1]>10:
            group.append(x)
        
    for x in Counter([x for path in peds.mapper.all_paths_fw(node) for x in path]).most_common(3):
        if x[0]!=0 and x[1]>10:
            group.append(x)

    return group

## Grouping algo
groups = {abs(n.node_id()):[abs(n.node_id()),] for n in tdg.get_all_nodeviews()}
print(len(groups))

cont=0
nodos=[abs(x) for x in groups.keys()]
for nodo in nodos:
#     changes = 0
    if nodo not in groups: continue
    neighbours = get_node_inmediate_neighbours(nodo)
    for nn in neighbours:
        if abs(nn[0]) not in groups:
            continue
        elif len(groups[abs(nn[0])]) > 1:
            break
        else:
            if nodo not in groups: continue
            groups[nodo].append(nn[0])
            groups.pop(abs(nn[0]))
#             changes+=1
            
#     nodo=random.choice(list(groups.keys()))
    print("\r%s" %(cont,), end='')
    cont += 1
    
len(groups)    
transformation = {}
for nodo_central, integrantes in groups.items():
    for integrante in integrantes:
        transformation[integrante] = nodo_central
        transformation[-integrante] = -nodo_central

trpm=[]
for rpm in lrr.read_perfect_matches:
    if len(rpm)<2: 
        trpm.append([])
        continue
    ms=[transformation[rpm[0].node]]
    for ix in range(1,len(rpm)-1):
        x=rpm[ix]
        g=transformation[x.node]
        if g==ms[-1]: continue #same group as before
        if ms[-1]==transformation[rpm[ix+1].node]: continue #sandwich
        ms.append(g)
    if transformation[rpm[-1].node]!=ms[-1]: ms.append(transformation[rpm[-1].node])
    trpm.append(ms)
```



# Gonza's implementation log


## Step 1: Create DBG
**Input:** PE reads
**Output:** DBG.
**Aim:** create a haplotype-specific DBG to have as a basis for unitig processing.

```python
K=63
MIN_CVG=10
NUM_BATCHES=1
ws=SDG.WorkSpace()
peds=ws.add_paired_reads_datastore('./pseudo_triploid_octoploid_new/octoploid_pe.prseq', 'pe')

gm=SDG.GraphMaker(ws.sdg)
gm.new_graph_from_paired_datastore(peds,K,MIN_CVG,NUM_BATCHES);

gc=SDG.GraphContigger(ws)
gc.clip_tips(2*K-1,3)
print("Remaining nodes: %s" %(len(ws.sdg.get_all_nodeviews())))
```

**Validation:**
- Kmer spectra
![](https://i.imgur.com/adARsBu.png)


- Unitig validity (i.e. sequence from all unitigs needs to be present in the reference): count and bp of unititgs found as perfect hits in the reference.

Found contigs 23705 with 2486607bp 

```
 # runs sdg-dbg with a realatively low coverage and tip clipping. too lowthreholds introduces a ton of errors as standalone contigs, min_cv changed from 2 to 10 ans now produces a better initial spectra and no errors in contigs
```

### Results in simulated Octoploid
```
for nv in ws.sdg.get_all_nodeviews():
    
```

## Step 2: Extract unique k-mer runs from unitigs
**Input:** DBG
**Output:** Strings with all their kmers being unique as per read frequencies.
**Aim:** create all unique k-mer runs, to be used as anchors
**Validation:**

- Each string must appear once and only once in the reference.
``` python
# - Each string must appear once and only once in the reference.
multimatch=[]
bps = 0
for k, v in encontrados.items():
    if len(v)>1 or len(v) == 0:
        print(k, v, ws.sdg.get_nodeview(k).sequence())
        bps+= ws.sdg.get_nodeview(k).size()
        multimatch.append(k)
print("Multi matching kmer runs %s/%s with %s bps" %(len(multimatch), len([x for x in ws.sdg.get_all_nodeviews()]), bps))
```

```
244 ['haplotype_1', 'haplotype_2'] GAATCTTTCACAGAAAAGCAGCTAGTAAATGA
473 ['haplotype_7', 'haplotype_8'] AAAGAAGGTTTTTTACAGCAAGAAGAAGAAGAGAA
1022 ['haplotype_1', 'haplotype_2'] CTCAGGAGATGCCAACAAATCAACTTCTGCCA
1023 ['haplotype_1', 'haplotype_2'] GAGATGCCAACAAATCAACTTCTGCCATTAA
1228 ['haplotype_2', 'haplotype_3'] AGTGTGGCGGTCACTGGGCCCACTGGTGGAAG
1973 ['haplotype_2', 'haplotype_3'] AGTGTGGCGGTCACTGGGCCCACTGGTGGAAG
3194 ['haplotype_1', 'haplotype_2'] ATGTTAATAGCAGTTTCCTGTCTGTCACCAGTTA
3244 ['haplotype_1', 'haplotype_2'] GCAGGGGATAGATGGTTGTTGGGGTGTGGTGATGGA
3497 ['haplotype_1', 'haplotype_2'] AACGATCATTATAGGCTAACTCTACCAGAAGACAAGA
3643 ['haplotype_2', 'haplotype_3'] ATCTCCCGGAAAGCTTATTTCTAAATAAAAT
3768 ['haplotype_2', 'haplotype_3'] ATCTCCCGGAAAGCTTATTTCTAAATAAAAT
4062 ['haplotype_1', 'haplotype_2'] AACGATCATTATAGGCTAACTCTACCAGAAGACAAGA
4756 ['haplotype_7', 'haplotype_8'] AACAAGGTCAAGAAAAAAATTAAAAAGGTTA
4820 ['haplotype_1', 'haplotype_2'] ACCTGGGTAATATTATGGTTACTACCAAGCC
4821 ['haplotype_1', 'haplotype_2'] CTTGGTAGTAACCATAATATTACCCAGGTAC
5013 ['haplotype_2', 'haplotype_3'] AGTGTGGCGGTCACTGGGCCCACTGGTGGAAG
6128 ['haplotype_2', 'haplotype_3'] ATCTCCCGGAAAGCTTATTTCTAAATAAAAT
7930 ['haplotype_1', 'haplotype_2'] CTTGGTAGTAACCATAATATTACCCAGGTAC
7931 ['haplotype_1', 'haplotype_2'] ACCTGGGTAATATTATGGTTACTACCAAGCC
8506 ['haplotype_7', 'haplotype_8'] AATGAAGAGATATTTAGAAACAGATTTTTTAGCACAAAGGCA
8668 ['haplotype_1', 'haplotype_2'] ACCTGGGTAATATTATGGTTACTACCAAGCC
8669 ['haplotype_1', 'haplotype_2'] CTTGGTAGTAACCATAATATTACCCAGGTAC
8711 ['haplotype_1', 'haplotype_2'] ATGTTAATAGCAGTTTCCTGTCTGTCACCAGTTA
8714 ['haplotype_1', 'haplotype_2'] AACGATCATTATAGGCTAACTCTACCAGAAGACAAGA
Multi matching kmer runs 24/8768 with 793 bps
```

- All kmers between fmin and fmax must be used ONCE in the strings.

``` python
# - All kmers between fmin and fmax must be used ONCE in the strings.
nodes_with_repeated_content=[]
cant = 0
for i, nv in enumerate(ws.sdg.get_all_nodeviews()):
    kcov = nv.kmer_coverage('pek31', 'sdg')
    if sum(kcov) !=  len(kcov):
        cant += 1
        print(nv, kcov, nv.node_id() in multimatch)
print("Kmers multi represented in the graph %s" %cant)
```

```
<NodeView: node 1228 in graph SDG> [3, 3] True
<NodeView: node 1973 in graph SDG> [3, 3] True
<NodeView: node 3194 in graph SDG> [2, 2, 2, 2] True
<NodeView: node 3497 in graph SDG> [3, 3, 3, 3, 3, 3, 3] True
<NodeView: node 3643 in graph SDG> [3] True
<NodeView: node 3768 in graph SDG> [3] True
<NodeView: node 4062 in graph SDG> [3, 3, 3, 3, 3, 3, 3] True
<NodeView: node 4820 in graph SDG> [3] True
<NodeView: node 4821 in graph SDG> [3] True
<NodeView: node 5013 in graph SDG> [3, 3] True
<NodeView: node 6128 in graph SDG> [3] True
<NodeView: node 7930 in graph SDG> [3] True
<NodeView: node 7931 in graph SDG> [3] True
<NodeView: node 8668 in graph SDG> [3] True
<NodeView: node 8669 in graph SDG> [3] True
<NodeView: node 8711 in graph SDG> [2, 2, 2, 2] True
<NodeView: node 8714 in graph SDG> [3, 3, 3, 3, 3, 3, 3] True
Kmers multi represented in the graph 17
```


- All strings should ONLY be formed by k-mers between fmin and fmax.

``` python
cant = 0
for i, nv in enumerate(ws.sdg.get_all_nodeviews()):
    kcov = [x>=40 and x <=100 for x in nv.kmer_coverage('pek31', 'pe')]
    if sum(kcov) != len(kcov):
        print(nv, nv.kmer_coverage('pek31', 'pe'))
        cant += 1
```

```
None present
```

## Step 3: group kmer runs with PE reads
**Input:** unique kmer runs (as nodes) + PE datastore.
**Output:** groups as lists of SIGNED nodes.
**Aim:** group immediate neighbouring unique kmer runs into longer structures to allow better sensitivity when linking with long reads.
**Validation:**
- Each node can ONLY APPEAR in one group.

```python
## Prop check
count = Counter()
for k, v in groups.items():
    for x in v:
        count[abs(x)]+=1
        
for c in count.most_common():
    if c[1]>1:
        print(c)
```

```
Nothing reported, checks
```

- Each group should have no more than MAX_SIZE total k-mers.
    - total size does not appear to be a problem atm

- unique kmer runs of each group should appear consecutively and in-order in the reference. (no scrambling). I.e.: you can express each chromosome as a list of groups.
    - All group seems to be mutually excluyent, the superposition check 
    - Order within group seems to be ok, all the groups i checked grou "monotonically"

```python
## CHeck group superposicion
for g in group_c:
    for og in group_c:
        if g[0] == og[0]: continue
        if g[1]<og[1]<g[2] or g[1]<og[2]<g[2]:
            print("Groups %s and %s overlap %s -- %s" %(g, og, l, ol))

## Check order
group_c = []
for nc, group in groups.items():
    print("GROUP %s" %nc)
    print('seq'+',seq'.join([str(abs(x)) for x in group]))
    for i, node in enumerate(group[:-1]):
        pos=posicion[abs(group[i])][0][3]
        prev_pos=posicion[abs(group[i+1])][0][3]
        print('{:<4} {:>10}'.format(node, pos-prev_pos>0))
```



```
sedg=SDG.DistanceGraph(ws.sdg)
pedg=SDG.DistanceGraph(ws.sdg) # only links when there is no SE next.
for nv in ws.sdg.get_all_nodeviews(reverse=True):
    for p in peds.mapper.all_paths)fw(nv.node_id()):
        if len(p)==0: continue
        if p[0]!=0:
            sedg.add_link(-nv.node_id(), p[0], 1)
        else:
            pedg.add_link(-nv.node_id(), p[1], 1)

# Now linearise this graph. (pick the single most-connected node FW in SE first, and on PE if no connection passess the threshold in SE)

nextrundg=

#

```

**Something interesting, the errors joinhaplotypes even in the simulated octo**


## Step 4: link groups with long reads
**Input:** unique kmer runs (as nodes) + groups + LR datastore.
**Output:** a graph of group connectivity.
**Aim:** To discover the group composition of each haplotype.
**Validation:**
- On the graph: the best closest next should be the group that comes next in the reference. Same for prev.
- Linearly: express a chromosome as a list of anchors, put N's in the middle, it should be the same as masking all non-unique (fmin/fmax) k-mers from the reference. 


## Code dump
```python
import sys
sys.path.append('/Users/ggarcia/Documents/git_sources/sdg/cmake-build-relwithdebinfo/SDGpython/')
import SDGpython as SDG
from collections import Counter

## SDG-DBG assembly k=63
## Min coverage is important to reduce the noice
K=63
MIN_CVG=10
NUM_BATCHES=1
ws=SDG.WorkSpace()
peds=ws.add_paired_reads_datastore('./pseudo_triploid_octoploid_new/octoploid_pe.prseq', 'pe')

gm=SDG.GraphMaker(ws.sdg)
gm.new_graph_from_paired_datastore(peds,K,MIN_CVG,NUM_BATCHES);

gc=SDG.GraphContigger(ws)
gc.clip_tips(2*K-1,3)
print("Remaining nodes: %s" %(len(ws.sdg.get_all_nodeviews())))

ws.sdg.write_to_gfa1("./sdg_dbg_graph.gfa")

kc = ws.add_kmer_counter('pek31', 31)
kc.add_count('pe', peds)

seqs = gc.contig_reduction_to_unique_kmers(40, 100)            
print(len(seqs),"sequences for anchoring")
with open("pseudoLong2.fastq","w") as f:
    for s in seqs:
        f.write("@seq\n%s\n+\n%s\n"%(s,s))

## Recreate workspace using only unique kmer runs
ws=SDG.WorkSpace()
peds=ws.add_paired_reads_datastore('./pseudo_triploid_octoploid_new/octoploid_pe.prseq')
lords=ws.add_long_reads_datastore('./pseudo_triploid_octoploid_new/octoploid_long.loseq')
for s in seqs:
    ws.sdg.add_node(s)
print(len(ws.sdg.get_all_nodeviews()))
kc = ws.add_kmer_counter('pek31', 31)
kc.add_count('pe', peds)

lords = ws.add_long_reads_datastore('./pseudo_triploid_octoploid_new/octoploid_long.loseq')
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,31)
lrr.map(31)
lrr.thread_reads(end_matches=1)
tdg=lrr.dg_from_threads()
peds.mapper.path_reads(31)        

## Group nodes using the next neighbour
def make_neighbours_graph():
    sedg=SDG.DistanceGraph(ws.sdg)
    pedg=SDG.DistanceGraph(ws.sdg) # only links when there is no SE next.
    
    for nv in ws.sdg.get_all_nodeviews(both_directions=True):
        for p in peds.mapper.all_paths_fw(nv.node_id()):
            if len(p)==0: continue
            if p[0]!=0:
                sedg.add_link(-nv.node_id(), p[0], 0)
            else:
                pedg.add_link(-nv.node_id(), p[1], 0)

    return sedg, pedg

sedg, pedg = make_neighbours_graph()

sedg.write_to_gfa1("./neghbour_sedg.gfa")
pedg.write_to_gfa1("./neghbour_pedg.gfa")

## Remove multiple conections by voting the most popular
def pick_next(nv):
    winner = None
    poll = Counter([x.node().node_id() for x in nv.next()]).most_common(1)
    if len(poll)>0 and poll[0][1]>10:
        winner = poll[0][0]
    return winner

def clean_conections(idg):
    ndg = SDG.DistanceGraph(ws.sdg)
    for nv in idg.get_all_nodeviews(both_directions=True):
        winner = pick_next(nv)
        if winner is not None:
            if not ndg.are_connected(-nv.node_id(), winner):
                ndg.add_link(-nv.node_id(), winner, 0)
    return ndg

dg = clean_conections(sedg)
dg2 = clean_conections(pedg)

dg.write_to_gfa1("./neghbour_dg.gfa")
dg2.write_to_gfa1("./neghbour_dg2.gfa")

## Disconect multiple connections
for nv in dg.get_all_nodeviews(both_directions=True):
    if len(nv.next())>1:
        for con in nv.next():
            dg.remove_link(-nv.node_id(), con.node().node_id())
       
dg.write_to_gfa1("./neghbour_uniruns.gfa")

## group components
def get_components(dg):
    all_nodes = set([n.node_id() for n in dg.get_all_nodeviews()])
    groups={}
    while len(all_nodes)>0:
        node = all_nodes.pop()
        group = [node, ]
        fwn = dg.get_nodeview(node).next()
        while len(fwn)==1:
            next_node = fwn[0].node().node_id()
            if next_node in group: break
            group.append(next_node)
            all_nodes.remove(abs(next_node))
            fwn = dg.get_nodeview(next_node).next()


        group = [x*-1 for x in group[::-1]]
        fwn = dg.get_nodeview(-node).next()
        while len(fwn)==1:
            next_node = fwn[0].node().node_id()    
            if next_node in group: break                
            group.append(next_node)
            
            all_nodes.remove(abs(next_node))
            fwn = dg.get_nodeview(next_node).next()
        groups[node] = group    
    return groups
groups = get_components(dg)

## transformation table from nodo to group
print(len(groups))
transformation = {}
for nodo_central, integrantes in groups.items():
    for integrante in integrantes:
        transformation[integrante] = nodo_central
        transformation[-integrante] = -nodo_central

        ## transform reads to group mappings
        trpm=[]
        for rpm in lrr.read_perfect_matches:
            if len(rpm)<2: 
                trpm.append([])
                continue
            ms=[transformation[rpm[0].node]]
            for ix in range(1,len(rpm)-1):
                x=rpm[ix]
                g=transformation[x.node]
                if g==ms[-1]: continue #same group as before
                if ms[-1]==transformation[rpm[ix+1].node]: continue #sandwich
                ms.append(g)
            if transformation[rpm[-1].node]!=ms[-1]: ms.append(transformation[rpm[-1].node])
            trpm.append(ms)

## 
def gid_reads():
    """ collection to enter using the group id in absolute value and return the mapped reads to translate later"""
    gid2reads={abs(gid):set() for gid in groups.keys()}
    for i, read in enumerate(trpm):
        for gid in read:
            gid2reads[abs(gid)].add(i)
    return gid2reads

gid2reads = gid_reads()


def get_next(gid):
    c=Counter()
    trpm_ids = gid2reads[abs(gid)]
    ttrpm = [trpm[i] for i in trpm_ids]
    for x in ttrpm:
        if -gid in x: 
            x= [y*-1 for y in x[::-1]]
        if gid in x and x[-1] != gid:
            c[x[x.index(gid)+1]]+=1
    if len(c.most_common())>0 and c.most_common(1)[0][1]>3:
        return c.most_common(1)[0][0]
    else:
        None

ws2 = SDG.WorkSpace()
## Add all groups as nodes
group2node={}
node2group={}
all_nodes = [nodo for k, group in groups.items() for nodo in group]
for gid in groups.keys():
    group_size = sum([ws.sdg.get_nodeview(x).size() for x in groups[gid]])
    new_node = ws2.sdg.add_node("A"*group_size) 
    group2node[gid] = new_node
    node2group[new_node] = gid

print("%s nodes in ws2 graph" %len(ws2.sdg.get_all_nodeviews()))

# def runall():

for i, g in enumerate(groups.keys()):
    visited=set()
    prev_group = None
    next_group = get_next(g)
    while next_group is not None and next_group not in visited:
        if prev_group is not None:
            if prev_group<0: n1=-group2node[abs(prev_group)]
            else: n1=group2node[abs(prev_group)]

            if next_group<0: n2=-group2node[abs(next_group)]
            else: n2=group2node[abs(next_group)]

#                 print("%s --> %s" %(n1, n2))
            ws2.sdg.add_link(-n1, n2, 0, SDG.Support())

        visited.add(next_group)
        prev_group = next_group
        next_group = get_next(next_group)
    print("\r%s/%s" %(i, len(groups)), end='')
#         if i%50==1:
#             ws2.sdg.write_to_gfa1('./dummygraph.gfa')

ws2.sdg.write_to_gfa1('./dummygraph.gfa')
```