---
# System prepended metadata

title: BASF

---

# BASF


## Pacbio Scaffolding

Approach:

1 - Map reads, thread via 2+ matches.
2 - dg from threads.
3 - Anchors on nodes >500bp, kci<1.5, nextselected linkage.
4 - Linearise by reconnection of tips, bubbles and simple transitions. BW6_pbthreads.gfa -> 143Kbp N50.
5 - iterate over disconnected anchor nodes, check if they connect to lines (at their sides or in gaps, either thorugh PE or LR)
6 - reconnect disconnected lines (but mark their unions as "dangerous")

7 - Line to sequence:
7.a - fill with non-anchor nodes
7.b - check if there's overlaps on non-dangerous join
7.c - strider on non-dangerous joins
7.d - put Ns in non-dangerous joins
7.e- put ns in dangerous joins.

```python
import SDGpython as SDG

ws = SDG.WorkSpace('basf.pb.pe.kc.rf.sdgws')

print ("Calculating/loading KCI")
kc=ws.get_kmer_counter("all_pcrf_reads")
kc.load_cache('kc.dump')

print("Load PE mappings")
peds = ws.get_paired_reads_datastore('ei_pe')
peds.mapper.load_readpaths('pe_read_paths_k63.dump')

# Create PE strider
spe=SDG.Strider(ws)
spe.add_datastore(peds)

########################
# run lrr on pacbio reads
print ("Loading mapped PB reads and recruiting")
lords = ws.get_long_reads_datastore('pacbio')
k=31
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,k,50)
lrr.load("lrr_pb_k{0}_all_v3_r3.dump".format(k))

# long read strider
print("Run LRR")
s=SDG.Strider(ws)
s.add_datastore(lrr)
s.link_from_anchors(1000,.75,1.25,10000,2,2,0)
# min_size, min_kci, max_kci, distance, min_reads, group_size, small_node_size

def lines_stats(dg,min_size=1000):
    used_nodes=set()
    line_sizes=[]
    for nv in dg.get_all_nodeviews():
        n=nv
        if n.node_id() in used_nodes: continue
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.prev())==1 and len(n.prev()[0].node().next())==1:
            line_nodes.add(n.node_id())
            n=n.prev()[0].node()
        linebp=n.size()
        used_nodes.add(abs(n.node_id()))
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.next())==1 and len(n.next()[0].node().prev())==1:
            line_nodes.add(n.node_id())
            linebp+=n.next()[0].distance()
            n=n.next()[0].node()
            linebp+=n.size()
            used_nodes.add(abs(n.node_id()))
        if linebp>=min_size:
            line_sizes.append(linebp)
    line_sizes.sort(reverse=True)
    total_bp=sum(line_sizes)
    print("Total lines size %d bp\n"%total_bp)
    next_stop=0
    acc=0
    for count,size in enumerate(line_sizes,1):
        acc+=size
        if acc>=next_stop*total_bp/100:
            print("N%d: %d bp at line # %d"%(next_stop,size,count))
            next_stop+=10


from collections import Counter

#recruit on 2 matches (for later), create threads on 2 matches, make graph
print("recruiting")
lrr.recruit(1,2)
print("threading reads")
lrr.thread_reads(20000,2)
print("Constructing dg from threads")
trmldg2m=lrr.dg_from_threads(True)

#getting rid of all links not supported by at least two threads, self and big-overlap links, and links to inversions of better links
print("Cleaning links")
for nv in trmldg2m.get_all_nodeviews(both_directions=True):
    for l in nv.next():
        if l.distance()<-500 or abs(l.node().node_id())==abs(nv.node_id()):
            trmldg2m.remove_link(-nv.node_id(),l.node().node_id(),l.distance(),l.support())
    onf=Counter([x.node().node_id() for x in nv.next()])
    for onid in onf.keys():
        if onf[onid]<2: trmldg2m.remove_link(-nv.node_id(),onid)
        if onf[onid]<onf[-onid]: trmldg2m.remove_link(-nv.node_id(),onid)


print("Untangling linkage between anchors")
#Select anchor nodes by size and kci, create graph
lu=SDG.LinkageUntangler(trmldg2m)
for nv in trmldg2m.get_all_nodeviews(include_disconnected=False):
    if nv.size()>500 and nv.kci()<1.5:
        lu.selected_nodes[nv.node_id()]=True
trnsl=lu.make_nextselected_linkage(2)
trnsl.remove_transitive_links(3)
trnsl.write_to_gfa1('trnsl.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])


lines_stats(trnsl,1000)

# GRAPH linearisation heuristics

#find matches to other nodes between two nodes (in order) in the LR.
def nodes_matches_in_between(n1,n2):
    tc=Counter()
    for rid in set([abs(x) for x in lrr.node_reads[abs(n1)] + lrr.node_reads[abs(n2)]]):
        fwg=False
        c=Counter()
        bwg=False
        for pm in lrr.read_perfect_matches[rid]:
            if pm.node==n1:
                c=Counter()
                fwg=True
                bwg=False
            elif pm.node==-n2:
                c=Counter()
                fwg=False
                bwg=True
            elif pm.node==-n1:
                if bwg: tc+=c
                bwg=fwg=False
                c=Counter()
            elif pm.node==n2:
                if fwg: tc+=c
                bwg=fwg=False
                c=Counter()
            elif fwg: c[pm.node]+=1
            elif bwg: c[-pm.node]+=1
    return tc

print("Solving simple tips")
# Solve simple tips, where the tip is connected to a node and comes before the other connection.
simple_tips=0
simple_tips_solved=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.next())==2:
        if len(nv.next()[0].node().next())==0 and len(nv.next()[1].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
            simple_tips+=1
            nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
            if nmb[nv.next()[0].node().node_id()]>2:
                simple_tips_solved+=1
                trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
print("Simple tips:",simple_tips,simple_tips_solved)
trnsl.remove_transitive_links(3)
trnsl.write_to_gfa1('trnsl_tj.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)

print("Solving simple bubbles")
#Solve simple bubbles created by two small nodes who connect to their big neighbours
simple_bubbles=0
simple_bubbles_solved=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.next())==2 and len(nv.next()[0].node().parallels())==1 and  nv.next()[0].node().parallels()[0].node_id()==nv.next()[1].node().node_id():
            simple_bubbles+=1
            nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
            if nmb[nv.next()[0].node().node_id()]>2:
                simple_bubbles_solved+=1
                trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                trnsl.remove_link(-nv.next()[0].node().node_id(),nv.next()[1].node().next()[0].node().node_id())
                trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
print("Simple bubbles:",simple_bubbles,simple_bubbles_solved)
trnsl.remove_transitive_links(3)
trnsl.write_to_gfa1('trnsl_tj_bp.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)

for i in range(3):
    print("Solving simple transitions")
    # Solve simple tips, where the tip is connected to a node and comes before the other connection.
    simple_transitions=0
    simple_transitions_solved=0
    for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
        if len(nv.next())==2:
            if len(nv.next()[1].node().prev())==1 and len(nv.next()[0].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
                simple_transitions+=1
                nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
                if nmb[nv.next()[0].node().node_id()]>2:
                    simple_transitions_solved+=1
                    trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                    trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
    print("Simple transitions:",simple_transitions,simple_transitions_solved)
    trnsl.remove_transitive_links(3)

trnsl.write_to_gfa1('BW6_pbthreads.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)


def stride_between(s,n1,n2,verbose=False):
    spnn=[s.stride_out_in_order(n1).nodes,[-x for x in s.stride_out_in_order(-n2).nodes[::-1]],s.stride_out(n1).nodes,[-x for x in s.stride_out(-n2).nodes[::-1]]]
    for spn in spnn:
        if verbose: print(spn)
        if not (n1 in spn and n2 in spn): continue
        in1=spn.index(n1)
        in2=spn.index(n2)
        if verbose: print(in1,in2)
        if not in1<in2: continue
        return spn[in1:in2+1]
    return []


def line_to_sequence(dg,s,line,verbose=False):
    seq=dg.get_nodeview(line[0]).sequence()
    if verbose: print(len(seq))
    strided=overlapped=jumped=disconnected=0
    for i in range(len(line)-1):
        n1=line[i]
        nv1=dg.get_nodeview(n1)
        n2=line[i+1]
        nv2=dg.get_nodeview(n2)
        if verbose: print("+",len(nv2.sequence()))
        if dg.are_connected(-n1,n2):
            d=[x.distance() for x in nv1.next() if x.node().node_id()==n2][0]
            joined=False
            if d<-60:
                e1=nv1.sequence()[-200:]
                e2=nv2.sequence()[:200]
                for i in range (200,60,-1):
                    if e1[-i:]==e2[:i]:
                        if verbose: print("OVL ",i)
                        seq+=nv2.sequence()[i:]
                        joined=True
                        overlapped+=1
                        break
            if not joined and d<1000:
                spn=stride_between(s,n1,n2)
                if spn!=[]:
                    strided+=1
                    seq+=SDG.SequenceDistanceGraphPath(dg.sdg, spn[1:]).sequence()[199:]
                    joined=True
            if not joined:
                if verbose: print("Jump ",d)
                #TODO: try striding
                seq+='N'*max(100,d)+nv2.sequence()
                jumped+=1
        else:
            if verbose: print("Not connected ",d)
            seq+='n'*1000+nv2.sequence()
            disconnected+=1
        if verbose: print(len(seq))
    if verbose: print("strided",strided,"overlapped",overlapped,"jumped",jumped,"disconnected",disconnected)
    return seq

lines=trnsl.get_all_lines(1,2000)
with open("BW6_pbthreads_lines.fasta","w") as f:
    for i in range(len(lines)):
        f.write(">line%d "%i+str(lines[i])+"\n")
        f.write(line_to_sequence(trnsl,spe,lines[i])+"\n")

for i in range(3):
    print("Solving multi-transitions")
    # Solve simple tips, where the tip is connected to a node and comes before the other connection.
    simple_transitions=0
    simple_transitions_solved=0
    for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
        if len(nv.next())>=2:
            if len(nv.next()[1].node().prev())==1 and len(nv.next()[0].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
                simple_transitions+=1
                nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
                if nmb[nv.next()[0].node().node_id()]>2:
                    simple_transitions_solved+=1
                    trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                    trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
    print("Multi transitions:",simple_transitions,simple_transitions_solved)
    trnsl.remove_transitive_links(3)

def count_connections(nv,n2):
    return len([x for x in nv.next() if x.node().node_id()==n2])


print("Disconnecting low support crosses")
t=0
ec=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.prev())==1 and len(nv.next())==2:
        nn0=nv.next()[0].node()
        nn1=nv.next()[1].node()
        if len(nn0.next())!=1 or len(nn1.next())!=1: continue
        if len(nn0.prev())==2 and len(nn1.prev())==1:
            nn1,nn0=nn0,nn1
        if len(nn0.prev())==1 and len(nn1.prev())==2:
            t+=1
            o=[x.node().node_id() for x in nn1.prev() if x.node().node_id()!=nv.node_id()][0]
            l1c= count_connections(trmldg2m.get_nodeview(nv.node_id()),nn0.node_id())
            crossc= count_connections(trmldg2m.get_nodeview(nv.node_id()),nn1.node_id())
            l2c= count_connections(trmldg2m.get_nodeview(o),nn1.node_id())
            if l1c>6 and crossc<3 and l2c>6:
                ec+=1
                trnsl.remove_link(-nv.node_id(),nn1.node_id())

print("Low support crosses:",t,ec)
trnsl.remove_transitive_links(3)


print("Prunning tips with well-supported alternatives")

ec=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.prev())==1 and len(nv.next())>=2:
        abort=False
        to_keep=[]
        to_delete=[]
        for nl in nv.next():
            if len(nl.node().prev())!=1:
                abort=True
                break
            if len(nl.node().next())>0 and count_connections(trmldg2m.get_nodeview(nv.node_id()),nl.node().node_id())>2:
                to_keep.append(nl)
            elif nl.node().size()<2000 and len(nl.node().next())==0:
                to_delete.append(nl)
        if not abort and len(to_keep)==1 and len(to_delete)==len(nv.next())-1:
            ec+=1
            for ln in to_delete:
                trnsl.remove_link(-nv.node_id(),ln.node().node_id())
print("nodes with tips fw prunned:",ec)
trnsl.remove_transitive_links(3)

trnsl.write_to_gfa1('BW6_pbthreads_cleaner.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])

def get_node_pos(nid):
    try:
        return node_pos[nid]
    except:
        return (0,0,0)

def significant_next(lid,max_noise=2,min_ratio=5):
    if lid>0:
        if len(fw_links[lid])==0: return 0
        wn,wc=fw_links[lid].most_common(1)[0]
        for n,c in fw_links[lid].items():
            if n!=wn:
                if c>max_noise or c*min_ratio>wc:
                    return 0
        return wn
    else:
        if len(bw_links[-lid])==0: return 0
        wn,wc=bw_links[-lid].most_common(1)[0]
        for n,c in bw_links[-lid].items():
            if n!=wn:
                if c>max_noise or c*min_ratio>wc:
                    return 0
        return -wn

def get_next_line(lid,max_noise=2,min_ratio=5):
    sn=significant_next(lid,max_noise,min_ratio)
    if sn!=0 and significant_next(-sn,max_noise,min_ratio)==-lid:
        return sn
    return 0

def get_prev_line(lid,max_noise=2,min_ratio=5):
    return -get_next_line(-lid,max_noise,min_ratio)

def metaline(lid,max_noise=2,min_ratio=5):
    ml=[]
    p=lid
    while p!=0:
        if p in ml or -p in ml: return [lid]
        ml=[p]+ml
        p=get_prev_line(ml[0],max_noise,min_ratio)
    while ml[-1]!=0:
        n=get_next_line(ml[-1],max_noise,min_ratio)
        if n in ml or -n in ml: return [lid]
        ml.append(n)
    return ml[:-1]

##NEW METALINE DUMP
for min_line in [2000,5000]:
    print("Constructing metalines from %dbp+ lines with skipped connections"%min_line)

    lines=[[]]+[list(x) for x in trnsl.get_all_lines(1,min_line)]
    node_pos={}
    for lid in range(len(lines)):
        ll=len(lines[lid])
        for nidx in range(len(lines[lid])):
            node_pos[lines[lid][nidx]]=(lid,nidx,-ll+nidx)
            node_pos[-lines[lid][nidx]]=(-lid,ll-nidx,-nidx)

    fw_links=[]
    bw_links=[]
    for i in range(len(lines)):
        fw_links.append(Counter())
        bw_links.append(Counter())
        for nid in lines[i][:4]:
            for pl in trmldg2m.get_nodeview(nid).prev():
                plnp=get_node_pos(pl.node().node_id())
                if plnp==(0,0,0): continue
                if plnp[0]!=i: bw_links[-1][plnp[0]]+=1
        for nid in lines[i][-4:]:
            for nl in trmldg2m.get_nodeview(nid).next():
                nlnp=get_node_pos(nl.node().node_id())
                if nlnp==(0,0,0): continue
                if nlnp[0]!=i: fw_links[-1][nlnp[0]]+=1

    used=[False for x in lines]
    metalines=[]
    for i in range(1,len(lines)):
        if used[i]: continue
        metalines.append([])
        for lid in metaline(i):
            used[abs(lid)]=True
            if lid>0:
                metalines[-1]+=lines[lid]
            else:
                metalines[-1]+=[-x for x in lines[-lid][::-1]]
    print("dumping %d metalines"%len(metalines))
    with open("BW6_metalines%dk.fasta"%int(min_line/1000),"w") as f:
        for i in range(len(metalines)):
            f.write(">BW6_metaline%dk_%d "%(int(min_line/1000),i)+str(metalines[i])+"\n")
            f.write(line_to_sequence(trnsl,spe,metalines[i])+"\n")

```
```python
def count_duplicatednode_lines(mlines):
    c=Counter()
    for l in mlines:
        for x in l:
            c[abs(x)]+=1
    dl=[]
    for l in mlines:
        for x in l:
            if c[abs(x)]>1:
                dl.append(l)
                break
    print(len(dl))
```

```python

node_pos={}
for lid in range(len(lines)):
    ll=len(lines[lid])
    for nidx in range(len(lines[lid])):
        node_pos[lines[lid][nidx]]=(lid,nidx,-ll+nidx)
        node_pos[-lines[lid][nidx]]=(-lid,ll-nidx,-nidx)

def get_node_pos(nid):
    try:
        return node_pos[nid]
    except:
        return (0,0,0)

def line_between_report(line):
    for i in range(len(line)-1):
        print("\n%d -> %d bp -> %d"%(line[i],trnsl.get_nodeview(line[i]).next()[0].distance(),line[i+1]))
        mmc= [x for x in nodes_matches_in_between(line[i],line[i+1]).most_common() if x[1]>2]
        
        for x in mmc:
            cnv=trnsl.get_nodeview(x[0])
            print(x[1],"hits to node",x[0],cnv.size(),"bp",len(cnv.next()+cnv.prev()))
        print(spe.stride_out_in_order(line[i]).nodes)
        print([-x for x in spe.stride_out_in_order(-line[i+1]).nodes[::-1]])

```

# OLD CODE: Merging both graphs

We can use a thread method that is very unreliable, because we can always pop the node from the read later:

```python

def lines_stats(dg,min_size=1000):
    used_nodes=set()
    line_sizes=[]
    for nv in dg.get_all_nodeviews():
        n=nv
        if n.node_id() in used_nodes: continue
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.prev())==1 and len(n.prev()[0].node().next())==1:
            line_nodes.add(n.node_id())
            n=n.prev()[0].node()
        linebp=n.size()
        used_nodes.add(abs(n.node_id()))
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.next())==1 and len(n.next()[0].node().prev())==1:
            line_nodes.add(n.node_id())
            linebp+=n.next()[0].distance()
            n=n.next()[0].node()
            linebp+=n.size()
            used_nodes.add(abs(n.node_id()))
        if linebp>=min_size:
            line_sizes.append(linebp)
    line_sizes.sort(reverse=True)
    total_bp=sum(line_sizes)
    print("Total lines size %d bp\n"%total_bp)
    next_stop=0
    acc=0
    for count,size in enumerate(line_sizes,1):
        acc+=size
        if acc>=next_stop*total_bp/100:
            print("N%d: %d bp at line # %d"%(next_stop,size,count))
            next_stop+=10


from collections import Counter

#recruit on 2 matches (for later), create threads on 2 matches, make graph
print("recruiting")
lrr.recruit(1,2)
print("threading reads")
lrr.thread_reads(20000,2)
print("Constructing dg from threads")
trmldg2m=lrr.dg_from_threads(True)

#getting rid of all links not supported by at least two threads, self and big-overlap links, and links to inversions of better links
print("Cleaning links")
for nv in trmldg2m.get_all_nodeviews(both_directions=True):
    for l in nv.next():
        if l.distance()<-500 or abs(l.node().node_id())==abs(nv.node_id()):
            trmldg2m.remove_link(-nv.node_id(),l.node().node_id(),l.distance(),l.support())
    onf=Counter([x.node().node_id() for x in nv.next()])
    for onid in onf.keys():
        if onf[onid]<2: trmldg2m.remove_link(-nv.node_id(),onid)
        if onf[onid]<onf[-onid]: trmldg2m.remove_link(-nv.node_id(),onid)


print("Untangling linkage between anchors")
#Select anchor nodes by size and kci, create graph
lu=SDG.LinkageUntangler(trmldg2m)
for nv in trmldg2m.get_all_nodeviews(include_disconnected=False):
    if nv.size()>500 and nv.kci()<1.5:
        lu.selected_nodes[nv.node_id()]=True
trnsl=lu.make_nextselected_linkage(2)
trnsl.remove_transitive_links(1)
trnsl.write_to_gfa1('trnsl.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])


lines_stats(trnsl,1000)

# GRAPH linearisation heuristics

#find matches to other nodes between two nodes (in order) in the LR.
def nodes_matches_in_between(n1,n2):
    tc=Counter()
    for rid in set([abs(x) for x in lrr.node_reads[abs(n1)] + lrr.node_reads[abs(n2)]]):
        fwg=False
        c=Counter()
        bwg=False
        for pm in lrr.read_perfect_matches[rid]:
            if pm.node==n1:
                c=Counter()
                fwg=True
                bwg=False
            elif pm.node==-n2:
                c=Counter()
                fwg=False
                bwg=True
            elif pm.node==-n1:
                if bwg: tc+=c
                bwg=fwg=False
                c=Counter()
            elif pm.node==n2:
                if fwg: tc+=c
                bwg=fwg=False
                c=Counter()
            elif fwg: c[pm.node]+=1
            elif bwg: c[-pm.node]+=1
    return tc

print("Solving simple tips")
# Solve simple tips, where the tip is connected to a node and comes before the other connection.
simple_tips=0
simple_tips_solved=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.next())==2:
        if len(nv.next()[0].node().next())==0 and len(nv.next()[1].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
            simple_tips+=1
            nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
            if nmb[nv.next()[0].node().node_id()]>2:
                simple_tips_solved+=1
                trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
print("Simple tips:",simple_tips,simple_tips_solved)
trnsl.write_to_gfa1('trnsl_tj.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)

print("Solving simple bubbles")
#Solve simple bubbles created by two small nodes who connect to their big neighbours
simple_bubbles=0
simple_bubbles_solved=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.next())==2 and len(nv.next()[0].node().parallels())==1 and  nv.next()[0].node().parallels()[0].node_id()==nv.next()[1].node().node_id():
            simple_bubbles+=1
            nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
            if nmb[nv.next()[0].node().node_id()]>2:
                simple_bubbles_solved+=1
                trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                trnsl.remove_link(-nv.next()[0].node().node_id(),nv.next()[1].node().next()[0].node().node_id())
                trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
print("Simple bubbles:",simple_bubbles,simple_bubbles_solved)
trnsl.write_to_gfa1('trnsl_tj_bp.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)

for i in range(3):
    print("Solving simple transition")
    # Solve simple tips, where the tip is connected to a node and comes before the other connection.
    simple_transitions=0
    simple_transitions_solved=0
    for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
        if len(nv.next())==2:
            if len(nv.next()[1].node().prev())==1 and len(nv.next()[0].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
                simple_transitions+=1
                nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
                if nmb[nv.next()[0].node().node_id()]>2:
                    simple_transitions_solved+=1
                    trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                    trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
    print("Simple transitions:",simple_transitions,simple_transitions_solved)

trnsl.write_to_gfa1('trnsl_tj_bp_st.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)




#TODO: more aggressive here!
print("Solving and discarding simple tips")
# Solve simple tips, where the tip is connected to a node and comes before the other connection.
simple_tips=0
simple_tips_solved=0
simple_tips_discarded=0
for nv in trnsl.get_all_nodeviews(include_disconnected=False,both_directions=True):
    if len(nv.next())==2:
        if len(nv.next()[0].node().next())==0 and len(nv.next()[1].node().prev())==1 and nv.next()[0].node().size()+nv.next()[0].distance()<nv.next()[1].distance()+1000:
            simple_tips+=1
            nmb=nodes_matches_in_between(nv.node_id(),nv.next()[1].node().node_id())
            if nmb[nv.next()[0].node().node_id()]>2:
                simple_tips_solved+=1
                trnsl.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),nv.next()[1].distance()-nv.next()[0].node().size()-nv.next()[0].distance())
                trnsl.remove_link(-nv.node_id(),nv.next()[1].node().node_id())
            elif nv.next()[0].node().size()<2000:
                simple_tips_discarded+=1
                trnsl.remove_link(-nv.node_id(),nv.next()[0].node().node_id())
print("Simple tips:",simple_tips,simple_tips_solved)
trnsl.write_to_gfa1('trnsl_tj_bp_st_tj.gfa',selected_nodes=[x.node_id() for x in trnsl.get_all_nodeviews(include_disconnected=False)])
lines_stats(trnsl,1000)

```

```python
node_pos={}
for lid in range(len(lines)):
    ll=len(lines[lid])
    for nidx in range(len(lines[lid])):
        node_pos[lines[lid][nidx]]=(lid,nidx,-ll+nidx)
        node_pos[-lines[lid][nidx]]=(-lid,ll-nidx,-nidx)
def get_node_pos(nid):
    try:
        return node_pos[nid]
    except:
        return (0,0,0)
for x in trmldg2m.get_nodeview(lines[0][-2]).next(): print(x,x.node().size(),x.node().kci(),get_node_pos(x.node().node_id()))
for x in trmldg2m.get_nodeview(lines[0][-1]).next(): print(x,x.node().size(),x.node().kci(),get_node_pos(x.node().node_id()))

def other_line_connectivity_report(lid):
    print("\nother line connectivity for line",lid)
    print('seq'+',seq'.join([str(abs(x)) for x in lines[lid]]))
    print("BW")
    for nid in lines[lid]:
        c=Counter([get_node_pos(x.node().node_id())[0] for x in trmldg2m.get_nodeview(nid).prev()])
        print(c.most_common())
    print("FW")
    for nid in lines[lid]:
        c=Counter([get_node_pos(x.node().node_id())[0] for x in trmldg2m.get_nodeview(nid).next()])
        print(c.most_common())
        
for nid in nodes_3b:
    cb=Counter([get_node_pos(x.node().node_id())[0] for x in trmldg2m.get_nodeview(nid).prev()])
    cf=Counter([get_node_pos(x.node().node_id())[0] for x in trmldg2m.get_nodeview(nid).next()])
    print(get_node_pos(nid),nid,"BW:",cb.most_common(),"FW:",cf.most_common())
    

```

```python
for nid in nodes_3b:
    print("\nNode",nid, len(ws.sdg.get_nodeview(nid).prev()), len(ws.sdg.get_nodeview(nid).next()))
    print("BW:")
    for l in [(x.node().node_id(),x.distance()) for x in trmldg2m.get_nodeview(nid).prev()]: print(l)
    if nid>0: print([(-x.dest,x.dist) for x in s.links_bw[nid]])
    else: print([(-x.dest,x.dist) for x in s.links_fw[-nid]])
    print("FW:")
    for l in [(x.node().node_id(),x.distance()) for x in trmldg2m.get_nodeview(nid).next()]: print(l)
    if nid>0: print([(x.dest,x.dist) for x in s.links_fw[nid]])
    else: print([(x.dest,x.dist) for x in s.links_bw[-nid]])
    
    
    
for nid in nodes_3b:
    print("\nNode",nid, len(ws.sdg.get_nodeview(nid).prev()), len(ws.sdg.get_nodeview(nid).next()))
    print("BW:")
    for l in [(x.node().node_id(),x.distance()) for x in trnsl.get_nodeview(nid).prev()]: print(l)
    print("FW:")
    for l in [(x.node().node_id(),x.distance()) for x in trnsl.get_nodeview(nid).next()]: print(l)
```


```python
def dump_lrdg_to_text(dg,filename):
    with open(filename,"w") as f:
        for nv in dg.get_all_nodeviews( both_directions=True, include_disconnected=False):
            for l in nv.next():
                if -nv.node_id()<l.node().node_id():
                    f.write("%d %d %d\n"%(-nv.node_id(),l.node().node_id(),l.distance()))
                    
def load_lrdg_to_text(dg,filename):
    for l in open(filename).readlines():
        if not l: continue
        il=[int(x) for x in l.strip().split()]
        dg.add_link(il[0],il[1],il[2])
        
def clone_dg(odg):
    dg=SDG.DistanceGraph(odg.sdg)
    for nv in odg.get_all_nodeviews( both_directions=True, include_disconnected=False):
        for l in nv.next():
            if -nv.node_id()<l.node().node_id():
                dg.add_link(-nv.node_id(),l.node().node_id(),l.distance(),l.support())
    return dg
```
# Pacbio scaffolding procedure:

## First scaffolding round (on the w2rap graph)
1) Take the contigs_raw gfa from w2rap and create an SDG workspace with EI PE and PB datastores.
2) Generate a kmer counter using EI and BASF PE reads, add to workspace.
3) Calculate KCI.
4) Path PE reads (k=63). 
5) Map long reads and recruit, run strider.

```python
#This runs all steps 1-5, where there's no uncertainty
import SDGpython as SDG
ws = SDG.WorkSpace('basf.pb.pe.sdgws')
kc=ws.get_kmer_counter("all_pcrf_reads")
kc.set_kci_peak(88)
kc.compute_all_kcis()

peds = ws.get_paired_reads_datastore('ei_pe')
peds.mapper.path_reads(63,200)

lords = ws.get_long_reads_datastore('pacbio')
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,31,50)
lrr.map(31)
lrr.recruit(1,2)

s=SDG.Strider(ws)
s.add_datastore(lrr)
s.link_from_anchors(1000,.5,1.25,10000,2,2,0)

```
6) Create an LDG from strider links, find ALL LINES >1Kbp, stride between consecutive nodes, dump linear consensus.



```python
#This loads all steps 1-5, where there's no uncertainty
import SDGpython as SDG

ws = SDG.WorkSpace('basf.pb.pe.kc.rf.sdgws')

print ("Calculating/loading KCI")
kc=ws.get_kmer_counter("all_pcrf_reads")
kc.load_cache('kc.dump')

print("Load PE mappings")
peds = ws.get_paired_reads_datastore('ei_pe')
peds.mapper.load_readpaths('pe_read_paths_k63.dump')

# run PE strider
spe=SDG.Strider(ws)
spe.add_datastore(peds)

########################
# run lrr on pacbio reads
print ("Loading mapped PB reads and recruiting")
lords = ws.get_long_reads_datastore('pacbio')
k=31
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,k,50)
lrr.load("lrr_pb_k{0}_all_v3_r3.dump".format(k))

# long read strider
print("Run LRR")
s=SDG.Strider(ws)
s.add_datastore(lrr)
s.link_from_anchors(1000,.75,1.25,10000,2,2,0)
# min_size, min_kci, max_kci, distance, min_reads, group_size, small_node_size

```

Creating a graph:

```python

###Option A: only create reciprocal links:
def check_link_in_strider(s,end1,end2):
    r=[False,False]
    if end1<0:
        if [1 for l in s.links_fw[abs(end1)] if l.source==end1 and l.dest==end2]: r[0]=True
    else:
        if [1 for l in s.links_bw[abs(end1)] if l.source==end1 and l.dest==end2]: r[0]=True
    if end2<0:
        if [1 for l in s.links_fw[abs(end2)] if l.source==end2 and l.dest==end1]: r[1]=True
    else:
        if [1 for l in s.links_bw[abs(end2)] if l.source==end2 and l.dest==end1]: r[1]=True
    return r

def reciprocal_linked_anchors_dg(sdg,s):
    ag=SDG.DistanceGraph(ws.sdg)
    for nid,is_anchor in enumerate(s.is_anchor):
          if is_anchor:
            for l in s.links_bw[nid]+s.links_fw[nid]:
                if nid<abs(l.dest) and check_link_in_strider(s,l.source,l.dest)==[True,True]:
                    ag.add_link(l.source, l.dest, l.dist)
    return ag
```

## Second scaffolding round (on lines)

1) Load lines FASTA in SDG as a graph with disconnected nodes (use lines with A's rather than N's)
2) Add EI PE and PB datastores.
3) Create KCI (don't on the TEST) check in test if there are repeated nodes.
4) Map long reads and recruit (same parameters as before), run strider.
5) Create an LDG, dump lines with Ns, always, never try to recover connectivity as this is TAINTED by the fact that we threw all repeats away. (use lowercase ns to indicate these joins)
6) replace stretches of 100 or more 'A's with 'N's.

```python
ws.sdg.load_from_fasta("anchor_graph.fa")
ws.add_paired_reads_datastore("")
ws.add_long_reads_datastore("")

ws.add_kmer_counter("")
kc=ws.get_kmer_counter("all_pcrf_reads")
kc.set_kci_peak(88)
kc.compute_all_kcis()

lords = ws.get_long_reads_datastore('pacbio')
lrr=SDG.LongReadsRecruiter(ws.sdg,lords,31,50)
lrr.map(31)
lrr.recruit(1,2)

```

# Code dumps

```python
#BIG IDEA: tangle lr filter: take all reads recruited to the frontiers, filter their matches to only include internal nodes from the tangle, then tap?
def tangle_filtered_lrs(t,ws,lrr):
    print("\n\nAnaysing tangle with %d internals and %d frontiers")
    print('\nINTERNALS: seq'+',seq'.join([str(abs(x.node_id())) for x in t.internals]))
    print('\nFRONTIERS: seq'+',seq'.join([str(abs(x.node_id())) for x in t.frontiers])+"\n")
    for f in t.frontiers: print(f,f.kci())
    pmms=SDG.PerfectMatchesFilter(ws)
    nids=set()
    for x in list(t.internals)+list(t.frontiers):
        nids.add(x.node_id())
        nids.add(-x.node_id())
    onids=[-x.node_id() for x in t.frontiers]
    for f in t.frontiers:
        fdest=Counter()
        print("\n\nFrontier ", f,len(lrr.node_reads[abs(f.node_id())]))
        for rid in lrr.node_reads[abs(f.node_id())]:
            if f.node_id()>0: rid=-rid
            #print("\nRead ",rid," RAW")
            #for x in lrr.read_perfect_matches[abs(rid)]: print(x)
            if rid>0:
                om=lrr.read_perfect_matches[abs(rid)]
            else:
                om=lrr.reverse_perfect_matches(lrr.read_perfect_matches[abs(rid)])
            #print("\nRead ",rid," oriented matches")
            #for x in om: print(x)    
            mfn=[x for x in pmms.matches_fw_from_node(-f.node_id(),pmms.truncate_turnaround(om)) if x.node in nids]
            #print(len(mfn))
            mfn=[x for x in mfn if x.node in nids]
            #print(len(mfn))
            if len(mfn)==0 or mfn[0].node==f.node_id(): continue
            
            for x in mfn:
                if x.node in onids:
                    fdest[x.node]+=1
            print("\nRead ",rid)
            for x in mfn: print(x)
        print(fdest.most_common())
        
```

```python
lrr.recruit(1,2)
tangle_filtered_lrs(t,ws,lrr)
```



```python
def print_path(l):
    for nid in l:
        print(nid, ws.sdg.get_nodeview(nid).size())

def test_node(nid):
    print("\n\nNode connectivity for node",nid)
    print("\nBW in anchor_graph")
    for lf in anchor_graph.get_nodeview(nid).prev():
        print(lf,lf.node().size())
    print("stride_out_in_order (bw reversed)")
    for nn in spe.stride_out_in_order(-nid).nodes[1:][::-1]:
        print(-nn,ws.sdg.get_nodeview(nn).size())
    print("\nFW in anchor_graph")
    for lf in anchor_graph.get_nodeview(nid).next():
        print(lf,lf.node().size())
    print("stride_out_in_order")
    for nn in spe.stride_out_in_order(nid).nodes[1:]:
        print(nn,ws.sdg.get_nodeview(nn).size())

def test_node2(nid):
    print("\n\nNode connectivity for node",nid)
    npairs=set([int((abs(x)+1)/2)*2 for x in peds.mapper.paths_in_node[abs(nid)]])
    print("\nBW in anchor_graph")
    for lf in anchor_graph2.get_nodeview(nid).prev():
        fpairs=set([int((abs(x)+1)/2)*2 for x in peds.mapper.paths_in_node[abs(lf.node().node_id())]])
        print(lf,lf.node().size(),"\t\t%d shared pairs"%len(fpairs.intersection(npairs)))
    print("\nBW in multi graph")
    for lf in anchor_graph_multi.get_nodeview(nid).prev():
        fpairs=set([int((abs(x)+1)/2)*2 for x in peds.mapper.paths_in_node[abs(lf.node().node_id())]])
        print(lf,lf.node().size(),"\t\t%d shared pairs"%len(fpairs.intersection(npairs)))
    print("stride_out_in_order (bw reversed)")
    for nn in spe.stride_out_in_order(-nid).nodes[1:][::-1]:
        print(-nn,ws.sdg.get_nodeview(nn).size())
    print("\nFW in anchor_graph")
    for lf in anchor_graph2.get_nodeview(nid).next():
        fpairs=set([int((abs(x)+1)/2)*2 for x in peds.mapper.paths_in_node[abs(lf.node().node_id())]])
        print(lf,lf.node().size(),"\t\t%d shared pairs"%len(fpairs.intersection(npairs)))
    print("\nFW in multi graph")
    for lf in anchor_graph_multi.get_nodeview(nid).next():
        fpairs=set([int((abs(x)+1)/2)*2 for x in peds.mapper.paths_in_node[abs(lf.node().node_id())]])
        print(lf,lf.node().size(),"\t\t%d shared pairs"%len(fpairs.intersection(npairs)))
    print("stride_out_in_order")
    for nn in spe.stride_out_in_order(nid).nodes[1:]:
        print(nn,ws.sdg.get_nodeview(nn).size())
```

```python
to_bridge=set()
for fnv in anchor_graph2.get_all_nodeviews():
    for nv in [fnv,fnv.rc()]:
        if len(nv.next())==1 and len(nv.next()[0].node().prev())==1:
            nid=-nv.node_id()
            onid=nv.next()[0].node().node_id()
            to_bridge.add((min(nid,onid),max(nid,onid)))
```

```python
valid=invalid=too_short=not_ovl=0
#for n1,n2 in list(to_bridge)[:10000]:
for n1,n2 in list(to_bridge):
    nv=anchor_graph2.get_nodeview(-n1)
    d=nv.next()[0].distance()
    if d>-1: not_ovl+=1
    elif d>-20: too_short+=1
    elif d<-200: invalid+=1
    else:
        nvs=nv.sequence()
        nns=nv.next()[0].node().sequence()
        for pd in range(max(-199,d-50),min(-20,d+50)):
            if nvs[pd:]==nns[:-pd]:
                valid+=1
                break
        else:
            invalid+=1
print(valid,invalid,too_short,not_ovl)
```

```python
def lines_stats(dg,min_size=1000):
    used_nodes=set()
    line_sizes=[]
    for nv in dg.get_all_nodeviews():
        n=nv
        if n.node_id() in used_nodes: continue
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.prev())==1 and len(n.prev()[0].node().next())==1:
            line_nodes.add(n.node_id())
            n=n.prev()[0].node()
        linebp=n.size()
        used_nodes.add(abs(n.node_id()))
        line_nodes=set()
        while n.node_id() not in line_nodes and len(n.next())==1 and len(n.next()[0].node().prev())==1:
            line_nodes.add(n.node_id())
            linebp+=n.next()[0].distance()
            n=n.next()[0].node()
            linebp+=n.size()
            used_nodes.add(abs(n.node_id()))
        if linebp>=min_size:
            line_sizes.append(linebp)
    line_sizes.sort(reverse=True)
    total_bp=sum(line_sizes)
    print("Total lines size %d bp\n"%total_bp)
    next_stop=0
    acc=0
    for count,size in enumerate(line_sizes,1):
        acc+=size
        if acc>=next_stop*total_bp/100:
            print("N%d: %d bp at line # %d"%(next_stop,size,count))
            next_stop+=10
            
```

```python

def dump_lines(dg,filename,min_size=1000,spacing="N"*100):
    with open(filename,"w") as f:
        used_nodes=set()
        line_sizes=[]
        for nv in dg.get_all_nodeviews():
            n=nv
            if n.node_id() in used_nodes: continue
            line_nodes=set()
            while n.node_id() not in line_nodes and len(n.prev())==1 and len(n.prev()[0].node().next())==1:
                line_nodes.add(n.node_id())
                n=n.prev()[0].node()
            linebp=n.size()
            used_nodes.add(abs(n.node_id()))
            lineseq=n.sequence()
            line_nodes=set()
            while n.node_id() not in line_nodes and len(n.next())==1 and len(n.next()[0].node().prev())==1:
                line_nodes.add(n.node_id())
                linebp+=n.next()[0].distance()
                n=n.next()[0].node()
                linebp+=n.size()
                lineseq+=spacing+n.sequence()
                used_nodes.add(abs(n.node_id()))
            if linebp>=min_size:
                line_sizes.append(linebp)
                f.write(">%d\n%s\n"%(nv.node_id(),lineseq))
        line_sizes.sort(reverse=True)
        total_bp=sum(line_sizes)
        print("Total lines size %d bp\n"%total_bp)
        next_stop=0
        acc=0
        for count,size in enumerate(line_sizes,1):
            acc+=size
            if acc>=next_stop*total_bp/100:
                print("N%d: %d bp at line # %d"%(next_stop,size,count))
                next_stop+=10
```

```python
for nv in anchor_graph2.get_all_nodeviews():
    for l in list(nv.next())+list(nv.prev()):
        if l.distance()<-500 or abs(l.node().node_id())==nv.node_id():
            anchor_graph2.remove_link(-nv.node_id(),l.node().node_id())
```



```python

def matches_to_links(dg, matches, READSIZE=0, ENDSIZE=4000, ENDMATCHES=2, MAXDIFF=.1, MAXOVLP=1000, verbose=False):
    nstarts=[]
    nends=[]
    nodes=set([m.node for m in matches])
    if READSIZE==0: READSIZE=matches[-1].read_position
    for node in nodes:
        nmatches = [m for m in matches if m.node==node]
        smatches = [m for m in nmatches if m.node_position<ENDSIZE]
        ematches = [m for m in nmatches if m.node_position>dg.sdg.get_node_size(node) - ENDSIZE]
        if verbose and len(nmatches)>ENDMATCHES:
            print(node, dg.sdg.get_node_size(node), len(nmatches), len(smatches), len(ematches))
        if len(smatches) >= ENDMATCHES:
            nstart = smatches[0].read_position - smatches[0].node_position
        else:
            nstart = None
        if len(ematches) >= ENDMATCHES:
            nend = ematches[-1].read_position + dg.sdg.get_node_size(node) - ematches[-1].node_position
        else:
            nend = None
        if nstart is not None and nend is not None:
            if abs(nend - nstart - dg.sdg.get_node_size(node))/dg.sdg.get_node_size(node) < MAXDIFF:
                nstarts.append((nstart,node))
                nends.append((nend,node))
        if nstart is None and nend is not None:
            if nend - dg.sdg.get_node_size(node) < 0:
                nends.append((nend,node))
        if nstart is not None and nend is None:
            if nstart + dg.sdg.get_node_size(node) > READSIZE:
                nstarts.append((nstart,node))
    if verbose: print(nends,nstarts)
    for nend in nends:
        for nstart in nstarts:
            if nstart[0] - nend[0] >= -MAXOVLP:
                if verbose: print(-nend[1], nstart[1], nstart[0] - nend[0])
                dg.add_link( -nend[1], nstart[1], nstart[0] - nend[0] )

def analyse_recruited_reads(lrr,nodes):
    rids=set()
    for n in nodes:
        rids.update(lrr.node_reads[abs(n)])
    dg=SDG.DistanceGraph(ws.sdg)
    for rid in rids:
        #TODO: break looping reads
        matches_to_links(dg,lrr.read_perfect_matches[rid],ENDMATCHES=2)
    return dg
    

```


```python
def compare_graph_connectivity(g1,g2,min_size=1000):
    both=only_g1=only_g2=0
    for nv1 in g1.get_all_nodeviews():
        if nv1.size()<min_size: continue
        for ln in nv1.next():
            if ln.node().size()<min_size: continue
            if g2.are_connected(-nv1.node_id(),ln.node().node_id()):
                both+=1
            else:
                only_g1+=1
        for lp in nv1.prev():
            if lp.node().size()<min_size: continue
            if g2.are_connected(nv1.node_id(),-lp.node().node_id()):
                both+=1
            else:
                only_g1+=1
    for nv2 in g2.get_all_nodeviews():
        if nv2.size()<min_size: continue
        for ln in nv2.next():
            if ln.node().size()<min_size: continue
            if not g1.are_connected(-nv2.node_id(),ln.node().node_id()):
                only_g2+=1
        for lp in nv2.prev():
            if lp.node().size()<min_size: continue
            if not g1.are_connected(nv2.node_id(),-lp.node().node_id()):
                only_g2+=1
    print("both:",both,"   only_g1:",only_g1,"   only_g2:",only_g2)

def combine_graph_connectiviy(g1,g2):
    g=SDG.DistanceGraph(ws.sdg)
    for gx in [g1,g2]:
        for nv in gx.get_all_nodeviews():
            for ln in nv.next():
                if not g.are_connected(-nv.node_id(),ln.node().node_id()):
                    g.add_link(-nv.node_id(),ln.node().node_id(),ln.distance())
            for lp in nv.prev():
                if not g.are_connected(nv.node_id(),-lp.node().node_id()):
                    g.add_link(nv.node_id(),-lp.node().node_id(),lp.distance())
    return g
    
```



```python
#ultimate graph connectivity:
#start from reciprocal Strider links
#add non-reciprocal links if those linearise the graph (i.e. missed connections)
#add thread-created links if those linearise the graph
rladg=reciprocal_linked_anchors_dg(ws.sdg,s)
rladg.remove_transitive_links(3)
lrr.thread_reads(2000,2)


def classify_nodes(dg,min_size=0):
    disconnected=linear=branching=tips=multi_tips=repeats=0
    for nv in dg.get_all_nodeviews():
        if nv.size()<min_size: continue
        cn=len(nv.next())
        cp=len(nv.prev())
        if cp==0 and cn==0:
            disconnected+=1
        elif cp==0 or cn==0:
            if cp==1 or cn==1:
                tips+=1
            else:
                multi_tips+=1
        elif cp==1 and cn==1:
            linear+=1
        elif cp>1 and cn>1:
            repeats+=1
        else:
            branching+=1
    print("disconnected:",disconnected,"  linear:",linear,"  branching:",branching,"  tips:",tips,"  multi_tips:",multi_tips,"  repeats:",repeats)
```

```python
#add missed transitive connections
from statistics import median
for fnv in g3.get_all_nodeviews():
    for nv in [fnv,fnv.rc()]:
        if len(nv.next())==2:
            if not g3.are_connected(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id()) and thdg.are_connected(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id()):
                d=int(median([x.distance() for x in thdg.get_nodeview(nv.next()[0].node().node_id()).next() if x.node().node_id()==nv.next()[1].node().node_id()]))
                g3.add_link(-nv.next()[0].node().node_id(),nv.next()[1].node().node_id(),d)
                
                
                
#validate tips next!
does the tip have connections on threads? and on strider links?

tconn=notconn=0
from statistics import median
for fnv in g3.get_all_nodeviews():
    for nv in [fnv,fnv.rc()]:
        if len(nv.prev())==1 and len(nv.next())==0:
            if len(thdg.get_nodeview(nv.node_id()).next())>0:
                tconn+=1
                print('\n',nv)
                for x in thdg.get_nodeview(nv.node_id()).next():
                    print(x)
                if nv.node_id()>0:
                    for x in s2.links_fw[nv.node_id()]: print(x)
                else:
                    for x in s2.links_bw[-nv.node_id()]: print(x)
                if tconn>20: break
            else: notconn+=1
    if tconn>20: break
print(tconn,notconn)

#evaluate effect of reconnectig tips and connecting currently disconnected ends, when conneciton is unique forward
def extend_tips(sdg,dg1,dg2):
    g=SDG.DistanceGraph(sdg)
    connected_ends=reconnected_tips=joining_lines=0
    for fnv in dg1.get_all_nodeviews():
        for nv in [fnv,fnv.rc()]:
            for ln in nv.next():
                if not g.are_connected(-nv.node_id(),ln.node().node_id()):
                    g.add_link(-nv.node_id(),ln.node().node_id(),ln.distance())
            if len(nv.prev())==1 and len(nv.next())==0:
                tn=dg2.get_nodeview(nv.node_id()).next()
                if len(tn)==1 and len(tn[0].node().prev())==1 and tn[0].node().kci()>.5 and tn[0].node().kci()<1.5:
                    nnid=tn[0].node().node_id()
                    dg1pnext=nv.prev()[0].node().next()
                    dg1nprev=dg1.get_nodeview(nnid).prev()
                    dg1nnext=dg1.get_nodeview(nnid).next()

                    if len(dg1nprev)==0 and len(dg1pnext)==1:
                        connected_ends+=1
                        if len(dg1nnext)==1: joining_lines+=1
                        g.add_link(-nv.node_id(),nnid,tn[0].distance())
                    if len(dg1nprev)==1 and len(dg1pnext)==2 and dg1.are_connected(-nv.prev()[0].node().node_id(),nnid):
                        reconnected_tips+=1
                        g.add_link(-nv.node_id(),nnid,tn[0].distance())
    print(connected_ends,joining_lines,reconnected_tips)

def anihilate_tips(dg):
    c=0
    for fnv in dg.get_all_nodeviews():
        for nv in [fnv,fnv.rc()]:
            if len(nv.prev())==1 and len(nv.next())==2:
                n1=nv.next()[0]
                n2=nv.next()[1]
                if len(n1.node().next())>0: n2,n1=n1,n2
                if len(n1.node().next())==0 and len(n2.node().next())>0 and n1.distance()+n1.node().size()<n2.distance()+500:
                    dg.remove_link(-nv.node_id(),n1.node().node_id())
                    c+=1
    print(c)

u=SDG.LinkageUntangler(mthdg)
u.select_all()
mthnsl2=u.make_nextselected_linkage(2)

```