Commit 1da3d23b authored by Felix's avatar Felix
Browse files

optimize/clean up code

parent ea74dcb6
......@@ -14,27 +14,29 @@ class coalescence(cycle_tools_simple.simple,object):
def __init__(self):
super(coalescence,self).__init__()
self.cycle_tree=nx.Graph()
self.counter_c=0
def calc_cycle_asymmetry(input_graph):
minimum_basis=self.construct_minimum_basis(input_graph)
cycle_tree=self.calc_cycle_coalescence(input_graph,minimum_basis)
tree_asymmetry=self.calc_tree_asymmetry(cycle_tree)
minimum_basis=self.construct_networkx_basis(input_graph)
self.calc_cycle_coalescence(input_graph,minimum_basis)
tree_asymmetry=self.calc_tree_asymmetry()
return tree_asymmetry
def calc_cycle_coalescence(self,input_graph,cycle_basis):
#create cycle_map_tree with cycles' edges as tree nodes
self.G=nx.Graph(input_graph)
cycle_tree=nx.Graph()
#create cycle_map_tree with cycles' edges as tree nodes
for cycle in cycle_basis:
cycle_tree.add_node(tuple(cycle.edges(keys=True)),label='base',weight=1.,branch_type='none',pos=(-1,-1))
self.cycle_tree.add_node(tuple(cycle.edges()),label='base',weight=1.,branch_type='none',pos=(-1,-1))
# get the weights of the input graph and sort
edges=nx.get_edge_attributes(input_graph,'weight')
edges=nx.get_edge_attributes(self.G,'weight')
sorted_edges=sorted(edges,key=edges.__getitem__)
counter_c=0
# merge the cycles which share an edge
for e in sorted_edges:
......@@ -42,6 +44,7 @@ class coalescence(cycle_tools_simple.simple,object):
#check whether all cycles are merged
if len(cycle_basis)== 1:
break
cycles_with_edge={}
for i,cycle in enumerate(cycle_basis):
......@@ -54,67 +57,66 @@ class coalescence(cycle_tools_simple.simple,object):
cycle_1=cycle_basis[idx_list[0]]
cycle_2=cycle_basis[idx_list[1]]
cycles_edge_sets=[cycle_1.edges(keys=True),cycle_2.edges(keys=True)]
merged_cycle=self.merge_cycles(input_graph,cycle_1,cycle_2)
# build merging tree
if cycle_tree.nodes[tuple(cycles_edge_sets[0])]['label']=='base':
cycle_tree.nodes[tuple(cycles_edge_sets[0])]['pos']=(counter_c,0)
counter_c+=1
if cycle_tree.nodes[tuple(cycles_edge_sets[1])]['label']=='base':
cycle_tree.nodes[tuple(cycles_edge_sets[1])]['pos']=(counter_c,0)
counter_c+=1
merged_cycle=self.merge_cycles(cycle_1,cycle_2)
cycle_basis.remove(cycle_1)
cycle_basis.remove(cycle_2)
cycle_basis.append(merged_cycle)
# build up the merging tree, set leave weights to nodes, set asymetry value to binary branchings
cycle_keys=[tuple(cycles_edge_sets[0]),tuple(cycles_edge_sets[1]),tuple(merged_cycle.edges(keys=True))]
self.build_cycle_tree(cycle_tree,cycle_keys)
for n in cycle_tree.nodes():
if cycle_tree.nodes[n]['pos'][0]==-1:
cycle_tree.nodes[n]['pos']=(counter_c,0)
counter_c+=1
self.build_cycle_tree(cycle_1,cycle_2,merged_cycle)
for n in self.cycle_tree.nodes():
if self.cycle_tree.nodes[n]['pos'][0]==-1:
self.cycle_tree.nodes[n]['pos']=(self.counter_c,0)
self.counter_c+=1
else:
continue
return self.cycle_tree
return cycle_tree
def calc_tree_asymmetry(self,cycle_tree):
def calc_tree_asymmetry(self):
dict_asymmetry={}
for n in cycle_tree.nodes():
for n in self.cycle_tree.nodes():
if cycle_tree.nodes[n]['branch_type']=='vanpelt_2':
dict_asymmetry[n]=(cycle_tree.nodes[n]['asymmetry'])
if self.cycle_tree.nodes[n]['branch_type']=='vanpelt_2':
dict_asymmetry[n]=(self.cycle_tree.nodes[n]['asymmetry'])
return dict_asymmetry
def build_cycle_tree(self,cycle_tree,cycle_keys):
def build_cycle_tree(self,*args):
cycle_1,cycle_2,merged_cycle=args
cycles_edge_sets=[cycle_1.edges(),cycle_2.edges()]
cycle_keys=[tuple(cycles_edge_sets[0]),tuple(cycles_edge_sets[1]),tuple(merged_cycle.edges())]
c_weight=np.zeros(2)
# build merging tree
for i in range(2):
c_weight[i]=self.cycle_tree.nodes[cycle_keys[i]]['weight']
if self.cycle_tree.nodes[tuple(cycles_edge_sets[i])]['label']=='base':
self.cycle_tree.nodes[tuple(cycles_edge_sets[i])]['pos']=(self.counter_c,0)
c_x=(cycle_tree.nodes[cycle_keys[0]]['pos'][0]+cycle_tree.nodes[cycle_keys[1]]['pos'][0])/2.
c_y=np.amax([cycle_tree.nodes[cycle_keys[0]]['pos'][1],cycle_tree.nodes[cycle_keys[1]]['pos'][1]]) + 2.
c1_weight=cycle_tree.nodes[cycle_keys[0]]['weight']
c2_weight=cycle_tree.nodes[cycle_keys[1]]['weight']
self.counter_c+=1
cycle_tree.add_node(cycle_keys[2],pos=(c_x,c_y),label='merged',weight=c1_weight+c2_weight)
cycle_tree.add_edge(cycle_keys[0],cycle_keys[2])
cycle_tree.add_edge(cycle_keys[1],cycle_keys[2])
c_x=(self.cycle_tree.nodes[cycle_keys[0]]['pos'][0]+self.cycle_tree.nodes[cycle_keys[1]]['pos'][0])/2.
c_y=np.amax([self.cycle_tree.nodes[cycle_keys[0]]['pos'][1],self.cycle_tree.nodes[cycle_keys[1]]['pos'][1]]) + 2.
self.cycle_tree.add_node(cycle_keys[2],pos=(c_x,c_y),label='merged',weight=c_weight[0]+c_weight[1])
for i in range(2):
self.cycle_tree.add_edge(cycle_keys[i],cycle_keys[2])
# criterium for avoiding redundant branchings
if c_y>=6:
cycle_tree.nodes[cycle_keys[2]]['branch_type']='vanpelt_2'
cycle_tree.nodes[cycle_keys[2]]['asymmetry']=np.absolute((c1_weight-c2_weight))/(c1_weight+c2_weight-2.)
self.cycle_tree.nodes[cycle_keys[2]]['branch_type']='vanpelt_2'
self.cycle_tree.nodes[cycle_keys[2]]['asymmetry']=np.absolute((c_weight[0]-c_weight[1]))/(c_weight[0]+c_weight[1]-2.)
else:
cycle_tree.nodes[cycle_keys[2]]['branch_type']='none'
self.cycle_tree.nodes[cycle_keys[2]]['branch_type']='none'
def merge_cycles(self,input_graph,cycle_1,cycle_2):
def merge_cycles(self,cycle_1,cycle_2):
cycles_edge_sets=[cycle_1.edges(),cycle_2.edges()]
merged_cycle=nx.MultiGraph()
merged_cycle=nx.Graph()
merged_cycle.graph['cycle_weight']=0
for i in range(2):
for e in cycles_edge_sets[i]:
......@@ -124,7 +126,7 @@ class coalescence(cycle_tools_simple.simple,object):
merged_cycle.add_edge(*e)
for e in merged_cycle.edges():
merged_cycle.graph['cycle_weight']+=input_graph.edges[e]['weight']
merged_cycle.graph['cycle_weight']+=self.G.edges[e]['weight']
list_merged=list(merged_cycle.nodes())
for n in list_merged:
......@@ -133,195 +135,38 @@ class coalescence(cycle_tools_simple.simple,object):
return merged_cycle
def generate_cycle_lists(self,input_graph):
total_cycle_dict={}
total_cycle_list=[]
# check for graph_type, then check for paralles in the Graph, if existent insert dummy nodes to resolve conflict, cast the network onto simple graph afterwards
counter=0
self.G=nx.Graph(input_graph)
for n in input_graph.nodes():
# building new tree using breadth first
spanning_tree,dict_path=self.breadth_first_tree(n)
diff_graph=nx.difference(input_graph,spanning_tree)
labels_e={}
for e in diff_graph.edges():
p_in=nx.shortest_path(spanning_tree,source=n,target=e[0])
p_out=nx.shortest_path(spanning_tree,source=n,target=e[1])
# label pathways
simple_cycle=nx.MultiGraph(cycle_weight=0.)
nx.add_path(simple_cycle,p_in)
nx.add_path(simple_cycle,p_out)
simple_cycle.add_edge(*e)
list_n=list(simple_cycle.nodes())
seen={}
for m in list(simple_cycle.edges()):
num_conncetions=simple_cycle.number_of_edges(*m)
if num_conncetions > 1 and m not in seen.keys():
seen[m]=1
elif num_conncetions > 1:
seen[m]+=1
for m in seen:
for i in range(seen[m]):
simple_cycle.remove_edge(m[0],m[1],i)
for q in list_n:
if simple_cycle.degree(q)==0:
simple_cycle.remove_node(q)
if nx.is_eulerian(simple_cycle):
# relabeling and weighting graph
for m in simple_cycle.edges():
simple_cycle.graph['cycle_weight']+=1.
total_cycle_list.append(simple_cycle)
total_cycle_dict.update({counter:nx.number_of_edges(simple_cycle)})
counter+=1
return total_cycle_dict,total_cycle_list
def construct_minimum_basis(self,input_graph):
# calc minimum weight basis and construct dictionary for weights of edges, takes a leave-less, connected, N > 1 SimpleGraph as input, no self-loops optimally, deviations are not raising any warnings
#sort basis vectors according to weight, creating a new minimum weight basis from the total_cycle_list
nullity=nx.number_of_edges(input_graph)-nx.number_of_nodes(input_graph)+nx.number_connected_components(input_graph)
total_cycle_dict,total_cycle_list=self.generate_cycle_lists(input_graph)
sorted_cycle_list=sorted(total_cycle_dict,key=total_cycle_dict.__getitem__)
minimum_basis=[]
minimum_label=[]
EC=nx.MultiGraph()
counter=0
total_cycle_list_sort=[total_cycle_list[i] for i in sorted_cycle_list]
for c in sorted_cycle_list:
cycle_edges_in_basis=True
new_cycle=total_cycle_list[c]
for e in new_cycle.edges(keys=True):
if not EC.has_edge(*e):
EC.add_edge(*e,label=counter)
counter+=1
cycle_edges_in_basis=False
#if cycle edges where not part of the supergraph yet then it becomes automatically part of the basis
if not cycle_edges_in_basis:
minimum_basis.append(new_cycle)
aux_label=[EC.edges[e]['label'] for e in new_cycle.edges(keys=True) ]
minimum_label.append(aux_label)
#if cycle edges are already included we check for linear dependece
else:
E=self.edge_matrix(EC, len(minimum_basis), minimum_label ,new_cycle)
linear_independent=self.compute_linear_independence(E)
# print(linear_independent)
if linear_independent:
minimum_basis.append(new_cycle)
aux_label=[EC.edges[e]['label'] for e in new_cycle ]
minimum_label.append(aux_label)
if len(minimum_basis)==nullity:
break
if len(minimum_basis)<nullity:
sys.exit('Error: Cycle basis badly constructed')
return minimum_basis
def edge_matrix(self,*args):
EC,length_basis, minimum_label,new_cycle=args
rows=len(EC.edges())
columns=length_basis+1
E=np.zeros((rows,columns))
for i in range(length_basis):
E[minimum_label[i],i]=1
def compute_cycles_superlist(self,root):
for m in new_cycle.edges(keys=True):
E[EC.edges[m]['label'],-1]=1
spanning_tree,dict_path=self.breadth_first_tree(root)
diff_graph=nx.difference(self.G,spanning_tree)
list_cycles=[]
for e in diff_graph.edges():
return E
simple_cycle,cycle_edges=self.find_cycle(dict_path,e,root)
list_cycles.append(cycle_edges)
def compute_linear_independence(self,E):
linear_independent=False
rows=len(E[:,0])
columns=len(E[0,:])
# calc echelon form
a_columns=np.arange(columns-1)
for column in a_columns:
idx_nz=np.nonzero(E[column:,column])[0]
idx=idx_nz[0]+column
if len(idx_nz)==1:
E[[column,idx_nz[0]+column],:]=E[[idx_nz[0]+column,column],:]
else:
return list_cycles
new_idx=idx_nz[1:]+column
aux_E=np.add(E[new_idx],E[idx])
E[new_idx]=np.mod(aux_E,2)
E[[column,idx_nz[0]+column],:]=E[[idx_nz[0]+column,column],:]
def construct_networkx_basis(self,input_graph):
r=np.nonzero(E[columns-1:,-1])[0]
if r.size:
linear_independent=True
C=self.construct_minimum_basis(input_graph)
return linear_independent
networkx_basis=[]
for cs in C:
new_cycle=nx.Graph()
new_cycle.graph['cycle_weight']=0.
for e in cs:
def breadth_first_tree(self,root):
new_cycle.add_edge(*e)
for k,v in self.G.edges[e].items():
new_cycle.edges[e][k]=v
new_cycle.graph['cycle_weight']+=1.
T=nx.Graph()
push_down=nx.get_node_attributes(self.G,'push')
len_n=len(self.G.nodes())
for n in new_cycle.nodes():
if len(push_down.keys())!=len_n:
push_down={}
for n in self.G.nodes():
push_down[n]=False
for k,v in self.G.nodes[n].items():
new_cycle.nodes[n][k]=v
push_down[root]=True
root_queue=[]
labels=self.G.edges(root)
dict_path={}
dict_path[root]=[root]
T,dict_path,root_queue=self.compute_sprouts(root,T,labels ,push_down,dict_path,root_queue)
while T.number_of_nodes() < len_n:
new_queue=[]
for q in root_queue:
labels=self.G.edges(q)
T,dict_path,new_queue=self.compute_sprouts(q,T,labels,push_down,dict_path,new_queue)
root_queue=new_queue[:]
return T,dict_path
def compute_sprouts(self,*args):
root,T,labels,push_down,dict_path,root_queue=args
for e in labels:
if e[0]==root:
if not push_down[e[1]]:
T.add_edge(*e)
root_queue.append(e[1])
push_down[e[1]]=True
dict_path[e[1]]=dict_path[root]+[e[1]]
else:
if not push_down[e[0]]:
T.add_edge(*e)
root_queue.append(e[0])
push_down[e[0]]=True
dict_path[e[0]]=dict_path[root]+[e[0]]
networkx_basis.append(new_cycle)
return T,dict_path,root_queue
return networkx_basis
......@@ -21,8 +21,6 @@ class simple(cycle_tools.toolbox,object):
total_cycle_dict={}
total_cycle_list={}
self.G=nx.convert_node_labels_to_integers(self.G, first_label=0, ordering='default')
nx.set_node_attributes(self.G,False,'push')
# check for graph_type, then check for paralles in the Graph, if existent insert dummy nodes to resolve conflict, cast the network onto simple graph afterwards
......@@ -81,6 +79,28 @@ class simple(cycle_tools.toolbox,object):
return list_cycles
def construct_networkx_basis(self,input_graph):
C=self.construct_minimum_basis(input_graph)
networkx_basis=[]
for cs in C:
new_cycle=nx.Graph()
for e in cs:
new_cycle.add_edge(*e)
for k,v in self.G.edges[e].items():
new_cycle.edges[e][k]=v
for n in new_cycle.nodes():
for k,v in self.G.nodes[n].items():
new_cycle.nodes[n][k]=v
networkx_basis.append(new_cycle)
return networkx_basis
def construct_minimum_basis(self,input_graph):
# calc minimum weight basis and construct dictionary for weights of edges, takes a leave-less, connected, N > 1 SimpleGraph as input, no self-loops optimally, deviations are not raising any warnings
......@@ -94,7 +114,7 @@ class simple(cycle_tools.toolbox,object):
for c,e in total_cycle_dict.items():
total_cycle_len[c]=len(e)
sorted_cycle_list=sorted(total_cycle_len,key=total_cycle_len.__getitem__)
minimum_basis=[]
minimum_label=[]
EC=nx.Graph()
......@@ -120,7 +140,7 @@ class simple(cycle_tools.toolbox,object):
#if cycle edges are already included we check for linear dependece
else:
E=self.edge_matrix(EC, minimum_basis, minimum_label ,new_cycle)
E=self.edge_matrix(EC, len(minimum_basis), minimum_label ,new_cycle)
linear_independent=self.compute_linear_independence(E)
# print(linear_independent)
......@@ -137,37 +157,15 @@ class simple(cycle_tools.toolbox,object):
return minimum_basis
def construct_networkx_basis(self,input_graph):
C=self.construct_minimum_basis(input_graph)
networkx_basis=[]
for cs in C:
new_cycle=nx.Graph()
for e in cs:
new_cycle.add_edge(*e)
for k,v in self.G.edges[e].items():
new_cycle.edges[e][k]=v
for n in new_cycle.nodes():
for k,v in self.G.nodes[n].items():
new_cycle.nodes[n][k]=v
networkx_basis.append(new_cycle)
return networkx_basis
def edge_matrix(self,*args):
EC, minimum_basis, minimum_label,new_cycle=args
EC, length_basis, minimum_label,new_cycle=args
rows=len(EC.edges())
columns=len(minimum_basis)+1
columns=length_basis+1
E=np.zeros((rows,columns))
for idx_c,cycle in enumerate(minimum_basis):
E[minimum_label[idx_c],idx_c]=1
for i in range(length_basis):
E[minimum_label[i],i]=1
for m in new_cycle:
E[EC.edges[m]['label'],-1]=1
......
......@@ -2,12 +2,12 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
......@@ -20,25 +20,28 @@
],
"source": [
"import sys\n",
"sys.path.insert(0,'../cycle_analysis')\n",
"import cycle_tools_coalescence as ctc\n",
"import test as cat\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"import cycle_analysis.cycle_tools_coalescence as ctc\n",
"import cycle_analysis.test as cat\n",
"# import cycle_analysis.cycle_tools_coalescence as ctc\n",
"# import cycle_analysis.test as cat\n",
"\n",
"# generate a dummy graph for testing\n",
"# put an edge weight distribution on the system, available are random/gradient/bigradient/nested_square\n",
"n=5\n",
"n=7\n",
"G=nx.grid_graph(( n,n,1))\n",
"G=cat.generate_pattern(G,'random')\n",
"G=cat.generate_pattern(G,'nested_square')\n",
"\n",
"weights=[G.edges[e]['weight'] for e in G.edges()]\n",
"pos=nx.get_node_attributes(G,'pos')\n",
"\n",
"# merge all shortest cycles and create merging tree, then calc asymmetry of the tree's branches\n",
"T=ctc.coalescence()\n",
"minimum_basis=T.construct_minimum_basis(G)\n",
"minimum_basis=T.construct_networkx_basis(G)\n",
"cycle_tree=T.calc_cycle_coalescence(G,minimum_basis)\n",
"dict_asymmetry=T.calc_tree_asymmetry(cycle_tree)\n",
"dict_asymmetry=T.calc_tree_asymmetry()\n",
"\n",
"# plot branching asymmetry in dependence of branching level\n",
"x,y=[],[]\n",
......@@ -56,16 +59,10 @@
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"79.6 ms ± 2.85 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
]
}
],
"source": []
"outputs": [],
"source": [
"# %timeit T.construct_minimum_basis(G)"
]
},
{
"cell_type": "code",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment