Given a value, select an attribute using Networkx. Python - python

how do you tell Networkx that, given an attribute, to pick another one? More precisely, I have the following data:
Final_s1 = G.node[node]['s1']
Initial_s1 = G.node['a']['s1']
Final_s2 = G.node[node]['s2']
Initial_s2 = G.node['a']['s2']
I perform some easy calculations
Perf_s1 = (Final_s1 - Initial_s1)/Initial_s1
Perf_s2 = (Final_s2 - Initial_s2)/Initial_s2
I would like to see if there is any way of omitting the next two lines:
G.node[node]['Perf_s1'] = Perf_s1
G.node[node]['Perf_s2'] = Perf_s2
Next I find the minimum between both "performances":
min_node = min(['Perf_s1', 'Perf_s2'], key=lambda x: G.node[node][x])
This final part is what I want to learn how to program in a better way. It works, but having to add two additional attributes and a if-else statement does not look like the best thing to do.
if min_node == 'Perf_s1':
Initial_Worst = G.node['a']['s1']
Final_Worst = G.node[node]['s1']
G.node[node]['value'] = Initial_Worst * Final_Worst
else:
Initial_Worst = G.node['a']['s2']
Final_Worst = G.node[node]['s2']
G.node[node]['value'] = Initial_Worst*Final_Worst
Thanks!

Networkx doesn't support this kind of computation out of the box (and with reason). Nodes' attributes are simply kept as a dictionary, mapping attributes' names to values. What you're doing can be done in a more generic way with plain python code.
Here is my suggestion:
import random
import networkx as nx
# Generate a random graph with some values for s1 and s2:
G = nx.erdos_renyi_graph(10, 0.25)
nx.set_node_attributes(G, {node: {'s1': random.randint(0, 100), 's2': random.randint(0, 100)} for node in G.nodes()})
# Choose some node 'a' (the initial node):
a = 0
# Define the metrics we are interested at:
metrics = ['s1','s2']
# For each node compute and add the value:
for node in G.nodes():
# Compute the metrics (i.e., Perf_s1 and Perf_s2), keep in a dictionary:
metrics_dict = {metric: (G.node[node][metric] - G.node[a][metric]) / G.node[a][metric] for metric in metrics}
# Get the metric that minimizes the desired value (e.g., 's1'):
arg_min = min(metrics_dict, key=metrics_dict.get)
# Add as an attribute to the graph under 'value':
nx.set_node_attributes(G, {node : {'value': G.node[node][arg_min] * G.node[a][arg_min]}})

Related

CadQuery: Selecting an edge by index (Filleting specific edges)

I come from the engineering CAD world and I'm creating some designs in CadQuery. What I want to do is this (pseudocode):
edges = part.edges()
edges[n].fillet(r)
Or ideally have the ability to do something like this (though I can't find any methods for edge properties). Pseudocode:
edges = part.edges()
for edge in edges:
if edge.length() > x:
edge.fillet(a)
else:
edge.fillet(b)
This would be very useful when a design contains non-orthogonal faces. I understand that I can select edges with selectors, but I find them unnecessarily complicated and work best with orthogonal faces. FreeCAD lets you treat edges as a list.
I believe there might be a method to select the closest edge to a point, but I can't seem to track it down.
If someone can provide guidance that would be great -- thank you!
Bonus question: Is there a way to return coordinates of geometry as a list or vector? e.g.:
origin = cq.workplane.center().val
>> [x,y,z]
(or something like the above)
Take a look at this code, i hope this will be helpful.
import cadquery as cq
plane1 = cq.Workplane()
block = plane1.rect(10,12).extrude(10)
edges = block.edges("|Z")
filleted_block = edges.all()[0].fillet(0.5)
show(filleted_block)
For the posterity. To select multiple edges eg. for chamfering you can use newObject() on Workplane. The argument is a list of edges (they have to be cq.occ_impl.shapes.Edge instances, NOT cq.Workplane instances).
import cadquery as cq
model = cq.Workplane().box(10, 10, 5)
edges = model.edges()
# edges.all() returns worplanes, we have to get underlying geometry
selected = list(map(lambda x: x.objects[0], edges.all()))
model_with_chamfer = model.newObject(selected).chamfer(1)
To get edge length you can do something like this:
edge = model.edges().all()[0] # This select one 'random' edge
length = edge.objects[0].Length()
edge.Length() doesn't work since edge is Workplane instance, not geometry instance.
To get edges of certain length you can just create dict with edge geometry and length and filter it using builtin python's filter(). Here is a snippet of my implementation for chamfering short edges on topmost face:
top_edges = model.edges(">Z and #Z")
def get_length(edge):
try:
return edge.vals()[0].Length()
except Exception:
return 0.0
# Inside edges are shorter - filter only those
edge_len_list = list(map(
lambda x: (x.objects[0], get_length(x)),
top_edges.all()))
avg = mean([a for _, a in edge_len_list])
selected = filter(lambda x: x[1] < avg, edge_len_list)
selected = [e for e, _ in selected]
vertical_edges = model.edges("|Z").all()
selected.extend(vertical_edges)
model = model.newObject(selected)
model = model.chamfer(chamfer_size)

Problem with appending a graph object to lists for networkx in Python

I am trying to remove nodes at random from graphs using the networkx package. The first block describes the graph construction and the second block gives me the node lists that I have to remove from my graph H (20%, 50% and 70% removals). I want 3 versions of the base graph H in the end, in a list or any data structure. The code in block 3 gives me objects of type "None". The last block shows that it works for a single case.
I am guessing that the problem is in the append function, which somehow returns objects of type "None". I also feel that the base graph H might be getting altered after every iteration. Is there any way around this? Any help would be appreciated :)
import networkx as nx
import numpy as np
import random
# node removals from Graphs at random
# network construction
H = nx.Graph()
H.add_nodes_from([1,2,3,4,5,6,7,8,9,10])
H.add_edges_from([[1,2],[2,4],[5,6],[7,10],[1,5],[3,6]])
nx.info(H)
nodes_list = list(H.nodes)
# list of nodes to be removed
perc = [.20,.50,.70] # percentage of nodes to be removed
random_sample_list = []
for p in perc:
interior_list = []
random.seed(2) # for replicability
sample = round(p*10)
random_sample = random.sample(nodes_list, sample)
interior_list.append(random_sample)
random_sample_list.append(random_sample)
# applying the list of nodes to be removed to create a list of graphs - not working
graph_list = []
for i in range(len(random_sample_list)):
H1 = H.copy()
graph_list.append(H1.remove_nodes_from(random_sample_list[i]))
# list access - works
H.remove_nodes_from(random_sample_list[1])
nx.info(H)
Final output should look like:
[Graph with 20% removed nodes, Graph with 50% removed nodes, Graph with 7% removed nodes] - eg. list
The function remove_nodes_from does not return the modified graph, but returns None. Consequently, you only need to create the graph with the desired percentage of your nodes and append it to the list:
graph_list = []
for i in range(len(random_sample_list)):
H1 = H.copy()
H1.remove_nodes_from(random_sample_list[i])
graph_list.append(H1)

Calculate the longest path between two nodes NetworkX

I'm trying to make a Gantt chard using Networkx. All the nodes in the network are "tasks" that need to be performed to complete the project. With Networkx it is easy to calculate the total time of the project. But the make the Gantt chard I need the latest start of each node.
NetworkX includes one function(dag_longest_path_length) but this calculates to longest path in the whole network. Another function(astar_path_length) results in the shortest path between a source and node, but no function is availed which gives the longest path, or latest start in my case. (if a node as two predecessors it will take the fastest route, but in reality it also has to wait on the second before it can start.
I was thinking of one option.
To evaluate the previous attached nodes and selecting the longest path. Unformal I did not succeeded.
start_time=[]
time=0
DD=nx.DiGraph()
for i in range(df.shape[0]):
DD.add_edge(str(df.at[i,'blockT'])+'_'+df.at[i,'Task'], str(df.at[i,'blockS'])+'_'+df.at[i,'Succ'], weight=df.at[i,'duration'])
fig, ax = plt.subplots()
labels=[]
for i in range(df.shape[0]):
labels.append(str(df.at[i,'blockT'])+'_'+df.at[i,'Task'])
print(nx.astar_path_length(DD, '0_START', str(df.at[i,'blockT'])+'_'+df.at[i,'Task']) )
ax.broken_barh([(nx.astar_path_length(DD, '0_START', str(df.at[i,'blockT'])+'_'+df.at[i,'Task']), heuristic=None, weight='weight'),df.at[i,'duration'] )],(i-0.4,0.8), facecolors='blue' )
Here is some code that I use. I agree is really should be part of NetworkX because it comes up pretty often for me. graph must be a DiGraph. s is the source node and dist is a dict keyed by nodes with weighted distances to s as values.
def single_source_longest_dag_path_length(graph, s):
assert(graph.in_degree(s) == 0)
dist = dict.fromkeys(graph.nodes, -float('inf'))
dist[s] = 0
topo_order = nx.topological_sort(graph)
for n in topo_order:
for s in graph.successors(n):
if dist[s] < dist[n] + graph.edges[n,s]['weight']:
dist[s] = dist[n] + graph.edges[n,s]['weight']
return dist
Looks like you are using DAGs.
Your problem is rather rare so there is no built-in function for it in networkx. You should do it manually:
max(nx.all_simple_paths(DAG, source, target), key=lambda x: len(x))
Here is the full testing code:
import networkx as nx
import random
from itertools import groupby
# Create random DAG
G = nx.gnp_random_graph(50,0.3,directed=True)
DAG = nx.DiGraph([(u,v) for (u,v) in G.edges() if u<v])
# Get the longest path from node 1 to node 10
max(nx.all_simple_paths(DAG, 1, 10), key=lambda x: len(x))

How to find a path between 2 nodes based on in-between node's attribute's value using networkx graph?

I can search thru a tree and get shortest path between nodes using just simple:
nx.shortest_path(G, source=, target=)
But how can I choose a path going thru a node with particular attribute's value?
I have simple graph with nodes
G = nx.Graph()
for token in document:
G.add_node(token.orth_, item = token.i, tag = token.tag_, dep = token.dep_)
and edges:
for token in document:
for child in token.children:
G.add_edge(token.orth_, child.orth_, pitem = token.i, citem = child.i,
ptag = token.tag_, pdep = token.dep_, ctag = child.tag_, cdep = child.dep_)
Can I find simple solution because now i'm struggling to build complicated function.
EDIT
The idea is to have a function like this: (sketchy)
def getPathByNode(betw_word, betw_attr, src_word, src_attr, trg_word, trg_attr):
nx.shortest_path(G, source=src, source_attr=src_attr, target=trg, target_attr=trg_attr, through=betw_word, through_attr=betw_attr)
....
But of course not all parameters must be passed.
As inputs I'd take for example:
source_attr = {'dep_': 'ROOT'}
target_attr = {'tag_': 'NN'}
through = "of" or through = "from" or through_attr = {'tag_': 'IN'}
And et cetera. I'm currently trying to build recursion starting from the middle (through='from') and searchning for neighhbors but the same situatuion - missing attributes.
for i in G.neighbors("from"):
print(i)
i is just a string.
A simple solution would be computing all paths from source to target. Then just filter out all paths without a node that has the desired condition, and choose the shortest path among this set of paths. Assuming you have an undirected and unweighted graph, something like this should work:
import networkx as nx
# Generate a sample graph:
G = nx.barabasi_albert_graph(10, 3, seed=42)
print(G.edges())
def check_attribute(G, node):
# Say the condition is node id is 3:
return node == 3
valid_paths = []
for path in nx.all_simple_paths(G, source=0, target=7):
cond = False
for node in path:
if check_attribute(G, node):
cond = True
valid_paths.append(path)
break
lengths = [len(path) for path in valid_paths]
shortest_path = valid_paths[lengths.index(min(lengths))]
print('valid paths: {}'.format(valid_paths))
print('shortest_path: {}'.format(shortest_path))

How to get name property out of the result of eigenvalue in igraph?

I import a list of edges to igraph.
The data looks like this.
393795446 18215973
393795446 582203919
393795446 190709835
393795446 1093090866
393795446 157780872
393795446 1580109739
393795446 3301748909
393795446 1536791610
393795446 106170345
393795446 9409752
Which is something (source, target) of twitter followers.
I imported by running this code.
from igraph import *
import timeit
twitter_igraph = Graph.Read_Ncol('twitter_edgelist.txt', directed=True)
print twitter_igraph.summary()
IGRAPH DN-- 2869228 6764984 --
+ attr: name (v)
The import is completed and I can get any vertex back by twitter_igraph.vs.find(name='393795446')
However, when I run eigenvalue calculation it returns just a list of all the vertexes which I'm not sure how to reverse lookup to get the twitter id.
start = timeit.default_timer()
igraph_eg = twitter_igraph.evcent()
stop = timeit.default_timer()
print 'It takes {} seconds to finish'.format(stop - start)
igraph_eg.sort(reverse=True)
print igraph_eg[:10]
after I sort igraph_eg I can get the top 10 eigen value but I would like to show name property of that vertex. How do I print the name of the vertex with eigen value?
This is my workaround
np_vals = numpy.array(igraph_eg)
sorted_eigen = numpy.argsort(np_vals)[::-1][:20]
for eg in sorted_eigen:
print twitter_igraph.vs.find(eg)['name']
Try this:
ids = sorted(range(g.vcount()), key=igraph_eg.__getitem__, reverse=True)
names = g.vs[ids]["name"]
This will give you the IDs of the vertices in order of decreasing eigenvector centrality. However, since you are most likely interested only in the top few vertices, a partial sort using heaps might be faster:
from heapq import nlargest
ids = nlargest(10, xrange(g.vcount()), key=igraph_eg.__getitem__)
names = g.vs[ids]["name"]
This is for Python 2.x; if you are on Python 3.x, use range instead of xrange.
For what it's worth, your NumPy-based solution with argsort is equally good, so you could also use sorted_eigen from there as follows:
names = g.vs[sorted_eigen]["name"]
Note that you can plug a vertex index vector straight into g.vs[] to get a subset of vertices with those IDs.
This is by no means the best solution but it works. Keep in mind that it will only return a single node id, even when multiple nodes share the same eigenvector centrality score.
def eigenID(ev, graph):
'''
ev - eigenvector centrality value that you're locating
graph - the graph object that you're looking in
'''
g = graph
evs = g.evcent()
ids = g.vs['name']
ev_index = evs.index(ev)
node_id = ids[ev_index]
return node_id
I can refine this later on if it's not helpful.
Let me know.

Categories

Resources