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

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)

Related

Given a value, select an attribute using Networkx. 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]}})

Random ultrametric trees

I've implemented a program on python which generates random binary trees. So now I'd like to assign to each internal node of the tree a distance to make it ultrametric. Then, the distance between the root and any leaves must be the same. If a node is a leaf then the distance is null. Here is a node :
class Node() :
def __init__(self, G = None , D = None) :
self.id = ""
self.distG = 0
self.distD = 0
self.G = G
self.D = D
self.parent = None
My idea is to set the distance h at the beginning and to decrease it as an internal node is found but its working only on the left side.
def lgBrancheRand(self, h) :
self.distD = h
self.distG = h
hrandomD = round(np.random.uniform(0,h),3)
hrandomG = round(np.random.uniform(0,h),3)
if self.D.D is not None :
self.D.distD = hrandomD
self.distD = round(h-hrandomD,3)
lgBrancheRand(self.D,hrandomD)
if self.G.G is not None :
self.G.distG = hrandomG
self.distG = round(h-hrandomG,3)
lgBrancheRand(self.G,hrandomG)
In summary, you would create random matrices and apply UPGMA to each.
More complete answer below
Simply use the UPGMA algorithm. This is a clustering algorithm used to resolve a pairwise matrix.
You take the total genetic distance between two pairs of "taxa" (technically OTUs) and divide it by two. You assign the closest members of the pairwise matrix as the first 'node'. Reformat the matrix so these two pairs are combined into a single group ('removed') and find the next 'nearest neighbor' ad infinitum. I suspect R 'ape' will have a ultrametric algorhithm which will save you from programming. I see that you are using Python, so BioPython MIGHT have this (big MIGHT), personally I would pipe this through a precompiled C program and collect the results via paup that sort of thing. I'm not going to write code, because I prefer Perl and get flamed if any Perl code appears in a Python question (the Empire has established).
Anyway you will find this algorhithm produces a perfect ultrametric tree. Purests do not like ultrametric trees derived throught this sort of algorithm. However, in your calculation it could be useful because you could find the phylogeny from real data , which is most "clock-like" against the null distribution you are producing. In this context it would be cool.
You might prefer to raise the question on bioinformatics stackexchange.

Compute reachability of elements in a list of tuples

I have a list of tuples like this.
a = [(1,2),(1,3),(1,4),(2,5),(6,5),(7,8)]
In this list 1 relates to 2 and then 2 relates to 5 and 5 relates to 6 therefore 1 relates to 6. Similarly I need to find the relations between other elements in tuples. I need a function that takes the input values and outputs as follows:
input = (1,6) #output = True
input = (5,3) #output = True
input = (2,8) #output = False
I do not have knowledge of itertools or map functions. Can they be used to solve these types of problems?
And for the sake of curiosity and interest where can I find these types of questions to solve and where are these types of problems encountered in real life situations?
This can be easily done by considering the tuples as edges in a graph. The question is then reduced to checking if there is a path between the two nodes.
There exists lots of nice libraries for this, see e.g. networkx
import networkx as nx
a = [(1,2),(1,3),(1,4),(2,5),(6,5),(7,8)]
G = nx.Graph(a)
nx.has_path(G, 1, 6) # True
nx.has_path(G, 5, 3) # True
nx.has_path(G, 2, 8) # False
This answer here nicely states your problem as a graph problem, where every time you need to run your algorithm you need to check for the existence of a path between your input vertices. The time complexity for every query then depends on the size, order, diameter, degree of the underlying graph.
However, if you intend to run this algorithm many times with the same array a, it may be worth doing some preprocessing on the input graph to find the connected components (Wikipedia : connected components) first. In that case you can get constant time for every query. Here is the code I suggest :
# NOTE : tested using python 3.6.1
# WARNING : no input sanitization
a = [(1,2),(1,3),(1,4),(2,5),(6,5),(7,8)]
n = 8 # order of the underlying graph
# prepare graph as lists of neighbors for every vertex, i.e. adjacency lists (extra unused vertex '0', just to match the value range of the problem)
graph = [[] for i in range(n+1)]
for edge in a:
graph[edge[0]].append(edge[1])
graph[edge[1]].append(edge[0])
print( "graph : " + str(graph) )
# list of unprocessed vertices : contains all of them at the beginning
unprocessed_vertices = {i for i in range(1,n+1)}
# subroutine to discover the connected component of a vertex
def build_component():
component = [] # current connected component
curr_vertices = {unprocessed_vertices.pop()} # locally unprocessed vertices, initialize with one of the globally unprocessed vertices
while len(curr_vertices) > 0:
curr_vertex = curr_vertices.pop() # vertex to be processed
# add unprocessed neighbours of current vertex to the set of vertices to process
for neighbour in graph[curr_vertex]:
if neighbour in unprocessed_vertices:
curr_vertices.add(neighbour)
unprocessed_vertices.remove(neighbour)
component.append(curr_vertex)
return component
# main algorithm : graph traversal on multiple connected components
components = []
while len(unprocessed_vertices) > 0:
components.append( build_component() )
print( "components : " + str(components) )
# assign a number to each component
component_numbers = [None] * (n+1)
curr_number = 1
for comp in components:
for vertex in comp:
component_numbers[vertex] = curr_number
curr_number += 1
print( "component_numbers : " + str(component_numbers) )
# main functionality
def is_connected( pair ):
return component_numbers[pair[0]] == component_numbers[pair[1]]
# run main functionnality on inputs : every call is executed in constant time now, regardless of the size of the graph
print( is_connected( (1,6) ) )
print( is_connected( (5,3) ) )
print( is_connected( (2,8) ) )
I don't really know about the most likely situations where this problem could be encountered, but I suppose it can have application is some clustering tasks, or maybe if you want to know if it is possible to go from one place to another. If the edges of the graph represent dependencies between modules, this problem would tell you if two parts depend on each other, so maybe some potential applications in compiling or the managment of large projects. The underlying problem is a "Connected component" problem which is among the problems we know polynomial algorithms for.
It is generally very useful to model these kind of problems with graphs as these objects have a very simple structure, and most of the time we can reduce the original problem to a well known problem on graphs.

Graph-tool edge_property to string

I've got a graph with edge weights. I looked around and found that I can use edge_properties to represent an edge weight. I do it like this:
edge_weight = g.new_edge_property("double")
for i in range(10):
e = g.add_edge(i, i+1)
edge_weight[e] = i
Now I want to print a graph from this with the given edge weights on the edges. Do you have any ideas how to do this? The only thing that I could come up is this:
edge_weight = g.new_edge_property("double")
edge_str_weight = g.new_edge_property("string")
for i in range(10):
e = g.add_edge(i, i+1)
edge_weight[e] = i
edge_str_weight[e] = str(i)
graph_draw(g, edge_text=edge_str_weight, output="out.png")
It works, but it's quite redundant. Also if it's suggested to store the edge weight in an other structure or something, feel free to comment :)
In principle, there is no need create a different property, since a conversion to string will be made inside graph_draw(). However, graph-tool uses hexadecimal float notation by default, because it allows for a perfect representation. This is ideal for storing the values in a file, but not for displaying them. Therefore your approach is correct. You can perhaps do it more succinctly and efficiently using map_property_values():
label = g.new_edge_property()
map_property_values(edge_weight, label, lambda w: str(w))
graph_draw(g, edge_text=label, output="out.png"))
Maybe it's a typo but the assignment to edge_str_weight should reference the edge e you are currently working with:
edge_str_weight[e] = str(i)
Other than that, working with property maps is generally the best option with graph-tool. If for some reason you want to use a one-time property-map just for plotting purposes, you will again need to create one:
edge_alt_str_weights = g.new_edge_property("string")
for edge in g.edges():
edge_alt_str_weights[edge] = str(edge_weight[edge])
You might also want to define the property maps you plan to keep around as internal in case you want to use them persistently.

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